Cache all the things!
Author longemen3000
3 Stars
Updated Last
8 Months Ago
Started In
February 2020


Build Status

Bored of creating your cache each time when having a function f!(out,x)?

Problems defining a higher order jacobian with inplace functions?

Out of names for the output caches?

This package maybe can help you!


This package works with implace functions of the form: f(out,x), where:

  1. eltype(x) == eltype(out)
  2. x is of type Array,Dict,SparseVector,or SparseArray
  3. by default, the caches are not thread-safe or async safe. future releases will add special cached types to deal with this. as a workaround, you can try creating new cached functions instances using deepcopy(f)

help on easing those limits is appreciated.


This is the simplest way to use this Package:

#example inplace function
function f!(y,x)
    y[1] = exp(sum(x))
    y[2] = x[1]+x[2] - x[3]

x1 = rand(3)
x2 = rand(3)
x3 = rand(Float32,3)
x4 = rand(Float32,3)

f = CachedFunction(f!,3,2) #if multidimentional, use CachedFunction(f!,(1,2),(2,3))
f(x1) #allocates one time
f(x2) #all caches created and allocated! f(x2) is evaluated without additional allocations.
evaluate(f,x1) #other way to call the function
f(x3) #a specific cache for Float32 is created
f(x4) #no allocations, again.

Let's see a little bit more about what whe can do with this f

julia> f
cached version of f! (function with 2 cached methods)
julia> calls(f)
julia> cached_methods(f)
IdDict{DataType,Function} with 2 entries:
  Float64 => #198
  Float32 => #198

A dict with all cached closures for each type is stored in cached_methods(f). you can take one and use it if you want. If the cache doesn't exists, it's created on the fly during runtime.

What happens if i don't want to allocate during runtime?, The solution: use allocate!(f,Type)

julia> f
cached version of f! (function with 2 cached methods)
julia> allocate!(f,BigFloat)
julia> f
cached version of f! (function with 3 cached methods)

Accesing without evaluating

by default, a CachedFunction does not store any type of x, so calling f(x) will just use (or create) a cache for ´out´ . If you also want to store the input values, you can use evaluate!(f,x). you can access the input and output values stored for each type using the functions input(f,Type) and output(f,Type). allocate!(f,Type) also allocates a cache for x. lets see this in action:

x1 = [1.0,2.0,3.0]
evaluate!(f,x1) #x1 is stored
in1 = input(f,Float64) #accesses the input value when the eltype is a Float64
in1 == x1 #true
out1 = output(f,Float64) #accesses the output value when the eltype is a Float64

x2 = rand(3)
evaluate(f,x2) #evaluates on f2, the cache is changed, but x2 is not stored.
in2 = input(f,Float64) #x1 is stored here, not x2
in2 == x2 #false
out1 ==   output(f,Float64) #false
evaluate(f,x1) #restores the output cache to f(x1)
out1 ==   output(f,Float64) #true

I can do this myself, why did you do this?

The problem occurs when you need to calculate jacobians of jacobians. how many caches i need to create? of what types?

I like it! but i want more functionality

i'm open,really open to pull requests and issues. write something and we will se what we can do.