I have benchmarked several memoization implementations. The result is somewhat surprising. In theory, they work almost the same way. I wonder how it ends up with such a big difference. See this gist for details.
What are you actually trying to measure? @btime prints the minimum time over all iterations, and since the cache will contain the result for fib(40) at some point, youâre really just benchmarking cache lookup. Is that really what youâre interested in?
BTW, for your custom caching code the cache is a Dict{Any, Any}, and youâre basically doubling the hash computation cost by first doing a haskey and then a getindex. You could probably speed things up by using a Dict{Int, Int} and using get!. But if youâre going to go to the trouble of customizing for this particular application, why not use a Vector{Int} as the cache?
This is a very good point. But if thatâs the case then all of these options should be almost identical. I think Memoize.jl uses An IdDict. Maybe the overhead came from the construction of the key.
It is just a toy example. I am just interested in the best way to do memoization. Iâm in the process of writing a book, and this is one of the subject areas.
In the case of Caching.jl, a seemingly âlongâ time is spent on hashing the input arguments. You can check out (if interested) the hashing function here
For example:
Since it is being defined recusively, some time is spent on checking and inserting new values i.e.
julia> fib
fib (cache with 40 entries, 40 in memory 0 on disk)
I do have to say that nanosecond timings are not that relevant for the type of operations memoization is usually used for (i.e. operations that take orders of magnitude more time to complete i.e. ms/s for the increase in memory to be warranted).
In essence, all memoizers calculate hashes and lookup that hash in a dictionary however the type of hashing and checks performed have a slight performance penalty.
For small functions and very high speed i.e. ns-order, a custom memoizer is a much better choice.
StaticNumbers can also be used for compile-time âmemoizationâ of numeric functions (using Juliaâs dispatch table as the cache). With the fib function from that packageâs readme, I get:
This is cheating of course, since all of the computation takes place at compile time. As @tkoolen already said: @btime is probably not the most interesting benchmark for memoized functionsâŚ
Edit: I should probably mention that StaticNumbers is not intended for memoization. It just happens to work like that in this specific case because the compiler is able to do all the simplification.
Thank You for these benchmarks for Memoization performance ,
I understand your benchmarks and book here might have been before LRUCache
but the thread safe ( aka no race conditions ) use case is very important when you want reliable and predictable results like so "*A particular use case of this package is to implement function memoization for functions that can simultaneously be called from different threads."