Recommended way to cache results of expensive functions?

Suppose that I have a function f: Int --> Float64 that conducts some expensive computation, but the integer inputs into f typically have only a very small number of possible values. I thus want to cache the results for speedup. In languages like C/C++, we may use a local static variable. What is the Julian way to do this? A relevant topic is Caching the results of a factory method.

A straightforward method is to define a cache global variable in the enclosing module shown below.

module M

const cache = Dict{Int, Float64}()

function f(a::Int)
  if a ∉ keys(cache)
    # computate and store result in cache[a]
  end
  return cache[a]
end

end

However, if there are multiple functions that need cache, then there will be quite a few global variables like cache1, cache2 that seem a little messy.

1 Like

try this?

4 Likes

Or this

I don’t quite remember the differences but I’ve been using Memoization.jl in my code lately and it works great.

4 Likes

Just for completeness: DefaultDict in DataStructures.jl
https://juliacollections.github.io/DataStructures.jl/latest/default_dict/
Makes a good cache.

2 Likes

This is one way to solve that particular problem:

let cache = Dict{Int, Float64}()

    global function f(a::Int)
        if a ∉ keys(cache)
            # computate and store result in cache[a]
        end
        return cache[a]
    end
end
2 Likes

Thanks @GunnarFarneback . The above memoization packages are more convenient for result cache purposes in my original question. However, the let block version is more suitable for another scenario: to preallocate some memory in functions that are frequently used (i.e., in a for loop) but hide this detail from users.

let c = zeros(100)
    global function f(a::Vector, b::Vector)
        @assert length(a) == length(b) == 100
        c .= a .+ b
        # more operations on c
        # ...
    end
end

Previously, I usually pass c explicitly into the above function f, but removing c from the function interface seems much better.

Depends on how you use it. The obvious drawbacks are that you are limited to a fixed size and that it’s thread unsafe.

3 Likes