Caching computed properties

I have a class that has multiple computed properties.
The Getter looks up the value in a dictionary and then returns it.
The set stuff the dictionary.

I have a ‘bunch’ of these methods and I’d like to cache the value from the getter such that it doesn’t need to look it up but knows the value from the last call.

I believe I need to add two static properties for each of the getter/setters.
i.e.

Public Property mValue as integer
   static cached as boolean = false
   static cachevalue as integer

   if cached then
      return cachevalue
   else
      cachevalue = d.lookup(x)
      cached = true
   end if
   return cachevalue
End Get

I am wondering if that static cached variable is scoped correctly. Wil each instance of this class have it’s own state?

No. Check the documentation for Static: all instances of the class will share the same value for any Static variable.

2 Likes

Use normal properties instead of static here

1 Like

I dont think you’re saving any time here.

Is
d.lookup(x) faster or slower than
the overhead of the using a Get method, following the cachedvalue exists route?

Doesn’t the code above mean that (in best Highlander tradition), there can only ever be ONE value - the first one you ask for?
After that cachedvalue exists and you will always get that return value?

1 Like

Increasing slower when dictionaries gets bigger. Some keys can be random fast, others can be slower, and that’s a “random thing” depending on data distribution. Maybe that’s why he intended to cache. Some apps care about microseconds, because a lot of them saved gets the job done 5 minutes earlier, or some things just don’t work (correctly) if you don’t act at the allowed microseconds time band (hardware, protocols, etc).

But as Christian said, the design may be questionable, and maybe it just could not demand a dictionary.

I agree with others, this sounds like it may be a case of “premature optimization” where you go to a lot of work to save a little time, and it may not be necessary.

The general advice is: benchmark first, then optimize hotspots.

@Rick_Araujo is right that dictionary lookup can be slow in some cases, but in many cases it’s very fast and basically can be approximated as O(1) which means the time does not change based on the # of items in the dictionary. Technically it’s probably closer to O(log N) if the dictionary is using a balanced search tree.

By comparison. if you are using an Array, the time to find a value in the array (using Array.IndexOf ) does depend on the # of itmes and would be called O(N).

The profiler showed a ‘improvement’. (but i don’t ‘like’ the optimized code due to static scope.

And after

What’s the scope of what you’re doing here? How many items are in the dictionary? What’s the lookup key?

Dictionary uses a hash table, it means a jump NEAR the desired item and inspecting items until finding it to the end of its slot (bins) where it should be. So there’s a random number of steps. And the speed depends on size of slots, number of slots, hash algorithm, etc. And no, they don’t use trees for it.

1 Like

My class has a few common keys implemented as properties, where the rest of the properties need to be looked up in the dictionary and processed…
I’m thinking

Private Property mmFileSize as UInt64
Private Property mInitDone as Boolean = false
Private Property d as Dictionary

Public Sub Constructor()
  d = new Dictionary
  ...

   For Each prop As Introspection.Introspection.PropertyInfo In Introspection.GetType(obj).GetProperties
       // Check if the property has the CachedAttrib attribute
       If prop.GetAttributes.Lookup("CachedAttrib") <> Nil Then
           // Get the value of the property
           Dim v As Variant = prop.Value(obj)
       End If
   Next
   mInitDone = true
End Sub

Attributes ( CachedAttrib = true ) Public Property mFileSize as UInt64
Get
  if mInitDone then
     return mmFileSize
   else
      mmFileSize=d.lookup("FileSize")
   end if
   return mmFileSize
End Get
Set
  mmFileSize = value
End Set

End Property

Introspection seems to me the only sensible way to initialize the cache.
I have a few dozen properties like this that need to be refactored into the new pattern.

Was there any thoughts on if the profiles I submitted ‘demonstrated’ an improvement?

I see what you are going for here. My opinion

  1. Introspection is (almost) always the wrong solution. It’s slow and easy to break.
  2. Caching the properties in the constructor is not ideal:
  • it means that you load all cacheable properties up front, even if they might be properties that are never used even once
  • the class instance can’t be used until all that loading is done.

Imagine your class is a Book and one method is GetFirstPage() and another method is GetPageCount(). It’s easy to imagine a situation where you would want the former, without the latter, but if you force all cached properties to be loaded in the Constructor, you are probably adding delay, rather than reducing it.

1 Like

This is very “baroque”. How many properties are you needing to serve up?

1 Like

I am considering a method that would ‘fetch’ each element at ‘init’ time.

Understood. How many properties do you expect to do this with? I’ve asked several times and gotten no answer - it’s an important question.

1 Like

There are currently 64 properties.

I use a system like this in one of my projects where multiple long-running instances may change the values. They’re stored in a SQLite database that is read at runtime and refreshed periodically in all instances, then written in the Setters.

There shouldn’t be any conflicts due to how I’m using this and values shouldn’t be very stale as they check a flagging database value every minute to see if they need to reload.

It sounds like a fragile system, but with the right checks it works well.

I could be off here, but unless you plan to expand that in the future:
Dictionary call without caching is …probably? fast enough. 64 slots isnt going to cause the hash table to wander about massively.

Optimum (to my mind) would be an array of 64 values, addressed using enums instead of dictionary keys.

eg

ArrayOfThings(enumBanana) //straight to it

rather than

DictionaryOfThings.value(“Banana”) //lookup or cached value

Calling a for loop 512x512x2 is too slow. The profiler seems to be trying to convince me that there is a drastic improvement. Difficult to overlook.

Yeah. Sadly, without sight of the actual code (this is the first mention of a loop with 524288 iterations), it’s hard to get our teeth into this.

Off topic, but relatable:
This morning I was dealing with a customer who I had asked to ‘delete a folder’
They replied ‘I did what you asked but I couldnt delete it, so I checked the box and it still doesnt work’
I have no idea what they were looking at, and so it is here.

Is there any chance that the code can be shown?
What are you doing inside these nested loops?
Do you cache any of the values they use?

The actual value is a string that gets converted to an integer in an operator convert method.
The discussion is actually, should i cache the value…

P.S. Clairvoyance is now a prerequisite to technical support.
Tell them to open a dos box in admin mode and then navigate to the folder and make sure no finder window is open in the folder that is trying to be deleted and that no one else is using it. :wink: