# How to make a function store data to avoid repeating computation

**URL:** <https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396>\
**Category:** General Usage\
**Tags:** question, functions, style, memoize\
**Created:** [October 7, 2022, 11:45am UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396 "2022-10-07T11:45:50Z")\
**Posts on this page:** 20\
**Page:** 2

<div class="post-metadata">

**Author:** ![GunnarFarneback](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gunnarfarneback/32/1827_2.png) [@GunnarFarneback](https://discourse.julialang.org/u/GunnarFarneback)\
**Post date:** [October 8, 2022, 11:48am UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/21 "2022-10-08T11:48:07Z")

</div>

I would do it like this:

```julia
begin
    local is_computed = Ref(false)
    local result = Ref(0)
    function f()
        if !is_computed[]
            println("Heavy computations.")
            result[] = 1
            is_computed[] = true
        end
        return result[]
    end
end

```

This works also without the `Ref`s but runs into the dreaded [Issue #15726](https://github.com/JuliaLang/julia/issues/15276), which may or may not matter in practice depending on how you use the result.

---

<div class="post-metadata">

**Author:** ![Dan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dan/32/42581_2.png) [@Dan](https://discourse.julialang.org/u/Dan)\
**Post date:** [October 8, 2022, 11:53am UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/22 "2022-10-08T11:53:05Z")

</div>

Has anyone suggested using a closure in the following way:  
(I see now both @GunnarFarneback and @sgaure showed nice answers along this line)

```julia
julia> function lazify(f)
       val::Union{Nothing, Core.Compiler.return_type(f, Tuple{})} = nothing
       return () -> begin
           if isnothing(val)
               val = f()
           end
           return val
       end
       end
lazify (generic function with 1 method)

julia> function big_calc()
       sleep(1)
       return 10
       end
big_calc (generic function with 1 method)

julia> @time big_calc()
  1.002507 seconds (5 allocations: 144 bytes)
10

julia> lazy_big_calc = lazify(big_calc)
#3 (generic function with 1 method)

julia> @time lazy_big_calc()
  1.023378 seconds (4.07 k allocations: 213.197 KiB, 2.06% compilation time)
10

julia> @time lazy_big_calc()
  0.000015 seconds
10

```

`lazify(f)` returns a function which would do the long calculation the first time and return stored value later.  
This works for parameter-less functions.

As a general rule, I find using @eval anywhere except for generated code in compilation a little worrisome.

---

<div class="post-metadata">

**Author:** ![GunnarFarneback](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gunnarfarneback/32/1827_2.png) [@GunnarFarneback](https://discourse.julialang.org/u/GunnarFarneback)\
**Post date:** [October 8, 2022, 12:06pm UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/23 "2022-10-08T12:06:03Z")

</div>

> [@Dan](#):
>
> As a general rule, I find using @eval anywhere except for generated code in compilation a little worrisome.

That’s a sound gut reaction. I would recommend the same for undocumented internal functions like `Core.Compiler.return_type` (which as it happens also is something the Memoize package makes use of).

---

<div class="post-metadata">

**Author:** ![dlakelan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dlakelan/32/8491_2.png) [@dlakelan](https://discourse.julialang.org/u/dlakelan)\
**Post date:** [October 8, 2022, 12:08pm UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/24 "2022-10-08T12:08:51Z")

</div>

Why not just

```julia
const fval = f()

```

---

<div class="post-metadata">

**Author:** ![sgaure](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/sgaure/32/14779_2.png) [@sgaure](https://discourse.julialang.org/u/sgaure)\
**Post date:** [October 8, 2022, 12:18pm UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/25 "2022-10-08T12:18:51Z")

</div>

Because the whole point with this exercise is that it’s not known whether `f` will be called or not, and it takes a long time, so it should be avoided if not needed.

---

<div class="post-metadata">

**Author:** ![Dan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dan/32/42581_2.png) [@Dan](https://discourse.julialang.org/u/Dan)\
**Post date:** [October 8, 2022, 12:20pm UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/26 "2022-10-08T12:20:36Z")

</div>

> [@GunnarFarneback](#):
>
> That’s a sound gut reaction. I would recommend the same for undocumented internal functions like `Core.Compiler.return_type` (which as it happens also is something the Memoize package makes use of).

Agreed. Maybe `Core.Compiler.return_type` should be relegated to a default value of a Type parameter, so its use will not be hidden and could also be avoided.

---

<div class="post-metadata">

**Author:** ![Dan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dan/32/42581_2.png) [@Dan](https://discourse.julialang.org/u/Dan)\
**Post date:** [October 8, 2022, 12:50pm UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/27 "2022-10-08T12:50:40Z")

</div>

Taking your comments into consideration, and adding a _sentinel_ value to speed things up a bit more. Here is a new version:

```julia
julia> function sentinel_lazify(sentinel, f, args...; kwargs...)
       val = sentinel
       return () -> begin
           if val === sentinel
               val = f(args...; kwargs...)
           end
           return val
       end
       end
sentinel_lazify (generic function with 1 method)

julia> function big_calc_arg(x; double = false)
       sleep(1)
       return ifelse(double, 2x, x)
       end
big_calc_arg (generic function with 1 method)

julia> lazy_big_calc_arg = sentinel_lazify(NaN, big_calc_arg, 5.0; double = true)
#4 (generic function with 1 method)

julia> @time lazy_big_calc_arg()
  1.021969 seconds (17.83 k allocations: 1015.760 KiB, 1.90% compilation time)
10.0

julia> @time lazy_big_calc_arg()
  0.000007 seconds
10.0

```

It also allows taking arguments and keyword arguments which should be important for Big calculations.

---

<div class="post-metadata">

**Author:** ![dlakelan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dlakelan/32/8491_2.png) [@dlakelan](https://discourse.julialang.org/u/dlakelan)\
**Post date:** [October 8, 2022, 12:52pm UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/28 "2022-10-08T12:52:39Z")

</div>

Seems like for a thunk like this as the developer just call f, serialize the output, and ship it with the code.

```julia
const fval = deserialize("theval.data")

```

The part that’s weird here to me is that it’s a thunk. If it were a function with arguments… Sure. But a thunk is secretly just data right?

---

<div class="post-metadata">

**Author:** ![PeterSimon](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/petersimon/32/25193_2.png) [@PeterSimon](https://discourse.julialang.org/u/PeterSimon)\
**Post date:** [October 8, 2022, 5:33pm UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/29 "2022-10-08T17:33:39Z")

</div>

I don’t think I’ve seen anyone suggest using a `let` block. This is another standard Julia idiom for maintaining internal state in a function.

```julia
julia> Base.@kwdef struct MyOutputType
           # Struct holding whatever expensive-to-compute data fun needs to return
           data1::Vector{Float64} = Float64[]
           data2::Vector{Int} = Int[]
       end
MyOutputType

julia> let output = MyOutputType()
           global fun
           function fun()
               if isempty(output.data1) || isempty(output.data2)
                   # Do heavy computation of data1 and data2
                   sleep(1)
                   append!(output.data1, rand(3))
                   append!(output.data2, rand(Int, 2))
               end
               return output
           end
       end
fun (generic function with 1 method)

julia> @time fun()
  1.002328 seconds (14 allocations: 608 bytes)
MyOutputType([0.20769215447648282, 0.604679075531047, 0.1713726324228093], [-6078567609540524981, -1387886700698269969])

julia> @time fun()
  0.000001 seconds
MyOutputType([0.20769215447648282, 0.604679075531047, 0.1713726324228093], [-6078567609540524981, -1387886700698269969])

julia> using BenchmarkTools

julia> @btime fun()
  5.924 ns (0 allocations: 0 bytes)
MyOutputType([0.20769215447648282, 0.604679075531047, 0.1713726324228093], [-6078567609540524981, -1387886700698269969])

```

---

<div class="post-metadata">

**Author:** ![natema](https://avatars.discourse-cdn.com/v4/letter/n/ba9def/32.png) [@natema](https://discourse.julialang.org/u/natema)\
**Post date:** [October 8, 2022, 6:56pm UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/30 "2022-10-08T18:56:20Z")

</div>

@ArthurW linked to [such an example in the standard library](https://github.com/JuliaLang/julia/blob/master/stdlib/Random/src/misc.jl#L72) in a former version of one of his comments, then it edited it away since he didn’t need the `let` statement there.  
For reference, here’s a tidier MWE of the approach:

```nohighlight
let isfirstcall = true, output = 0
   global function f()             
       isfirstcall || return output
       isfirstcall = false
       println("heavy computation")
       output = 42
       return output
   end
end

```

---

<div class="post-metadata">

**Author:** ![natema](https://avatars.discourse-cdn.com/v4/letter/n/ba9def/32.png) [@natema](https://discourse.julialang.org/u/natema)\
**Post date:** [October 9, 2022, 2:25pm UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/31 "2022-10-09T14:25:06Z")

</div>

(I made the previous MWE slightly more concise).  
I found [this 2017 JuliaCon talk by Jameson Nash](https://m.youtube.com/watch?t=93&v=7KGZ_9D_DbI) that addresses memoization, showing a more general example to memoize any input, and also a macro that looks like what Memoize.jl is essentially doing.

Unless at some point the discussion will show a different majority consensus, I’d summarize the solution for other users stumbling upon this thread:  
For general memoization it is recommended to check out [Memoize.jl](https://github.com/JuliaCollections/Memoize.jl).  
For lazy construction of an object, this thread illustrates different approaches.

---

<div class="post-metadata">

**Author:** ![natema](https://avatars.discourse-cdn.com/v4/letter/n/ba9def/32.png) [@natema](https://discourse.julialang.org/u/natema)\
**Post date:** [October 13, 2022, 11:37am UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/32 "2022-10-13T11:37:22Z")

</div>

Out of curiosity, I did a small benchmark.  
First, we see that using a closure is 10x faster than defining a global, as proposed in the initial post:

```nohighlight
julia> function f_global()
           isdefined(@ __MODULE__ , :_output) && return _output
           global _output = 42
           return _output
       end
f_global (generic function with 1 method)

julia> f_global()
42

julia> @benchmark f_global()
BenchmarkTools.Trial: 10000 samples with 999 evaluations.
 Range (min … max): 12.185 ns … 54.210 ns ┊ GC (min … max): 0.00% … 0.00%
 Time (median): 13.612 ns ┊ GC (median): 0.00%
 Time (mean ± σ): 13.818 ns ± 1.758 ns ┊ GC (mean ± σ): 0.00% ± 0.00%

         ▃▂▇▅█▇▄▄▁                                             
  ▂▂▂▄▃▆▇██████████▇▅▅▃▃▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▁▂▂▂▂▂▂▂▁▂▂▂▂ ▃
  12.2 ns Histogram: frequency by time 19.4 ns <

 Memory estimate: 0 bytes, allocs estimate: 0.

julia> let isfirstcall = true, output = 0
          global function f_closure()             
              isfirstcall || return output
              isfirstcall = false
              return output = 42
          end
       end
f_closure (generic function with 1 method)

julia> f_closure()
42

julia> @benchmark f_closure()
BenchmarkTools.Trial: 10000 samples with 1000 evaluations.
 Range (min … max): 1.622 ns … 3.031 ns ┊ GC (min … max): 0.00% … 0.00%
 Time (median): 1.663 ns ┊ GC (median): 0.00%
 Time (mean ± σ): 1.661 ns ± 0.060 ns ┊ GC (mean ± σ): 0.00% ± 0.00%

                                       ▂▄▄▄▃█                
  ▁▁▁▁▂▃▄▄▄▅▆▅▄▃▄▂▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▂▃▃▇▇████████▅▄▃▂▂▂▂▂▂▂▂▂▂ ▃
  1.62 ns Histogram: frequency by time 1.68 ns <

 Memory estimate: 0 bytes, allocs estimate: 0.

```

@ArthurW 's `@eval` gains a factor 2:

```nohighlight
julia> function f_eval()
           output = 42
           @eval f_eval() = $output
           return output
       end
f_eval (generic function with 1 method)

julia> f_eval()
42

julia> @benchmark f_eval()
BenchmarkTools.Trial: 10000 samples with 1000 evaluations.
 Range (min … max): 0.809 ns … 3.450 ns ┊ GC (min … max): 0.00% … 0.00%
 Time (median): 0.831 ns ┊ GC (median): 0.00%
 Time (mean ± σ): 0.831 ns ± 0.062 ns ┊ GC (mean ± σ): 0.00% ± 0.00%

                                 ▆█ █▇                       
  ▂▂▃▁▄▄▁▆█▇▁▇▅▁▃▃▁▃▃▂▁▂▂▁▂▃▁▆▆█▁██▁██▁█▄▄▁▄▃▁▃▃▁▂▂▂▁▂▂▁▂▂▂ ▃
  0.809 ns Histogram: frequency by time 0.849 ns <

 Memory estimate: 0 bytes, allocs estimate: 0.

```

EDIT: In a previous version of this post I was using an older version of @ArthurW’s `@eval` trick, which was still defining a global; I updated the post with the last version.

---

<div class="post-metadata">

**Author:** ![JackStrauss](https://avatars.discourse-cdn.com/v4/letter/j/f14d63/32.png) [@JackStrauss](https://discourse.julialang.org/u/JackStrauss)\
**Post date:** [October 14, 2022, 7:09am UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/33 "2022-10-14T07:09:42Z")

</div>

> [@PetrKryslUCSD](#):
>
> I think globals are bad. Is there even one example of code that _requires_ a global?

I could not avoid them. Have data that needs to be loaded and processed on module start and then the various module functions will use that data, some make output that other functions may need later.

The code runs for days/weeks/months, usually just waiting, and every hour or so checks if something new happened and then does things.

The alternative is passing all this data as some gigantic dictionary. Or cache it on disk, but since Julia does not guarantee saving dictionaries to binary file can be loaded again, not safe. (+unnecessary here) (I really wish julia had a safe way to story binary versions of data structures like R and Python)

I would love to do this without globals, but could not figure it out.

P.s. we used to run this code with cron, which avoids the globals, when it was python, but the startup time is so large that this replicating cron in julia is better. hence need for globals.

j

---

<div class="post-metadata">

**Author:** ![aplavin](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/aplavin/32/222056_2.png) [@aplavin](https://discourse.julialang.org/u/aplavin)\
**Post date:** [October 14, 2022, 9:57am UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/34 "2022-10-14T09:57:08Z")

</div>

> [@JackStrauss](#):
>
> Julia does not guarantee saving dictionaries to binary file can be loaded again

How exactly are you saving them to file? Serialization does guarantee that they’ll be readable by newer julia versions:

> The data format can change in minor (1.x) Julia releases, but files written by prior 1.x versions will remain readable.

---

<div class="post-metadata">

**Author:** ![JackStrauss](https://avatars.discourse-cdn.com/v4/letter/j/f14d63/32.png) [@JackStrauss](https://discourse.julialang.org/u/JackStrauss)\
**Post date:** [October 14, 2022, 10:27am UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/35 "2022-10-14T10:27:06Z")

</div>

> [@aplavin](#):
>
> How exactly are you saving them to file? Serialization does guarantee that they’ll be readable by newer julia versions:

OOps, the last time I checked [Serialization · The Julia Language](https://docs.julialang.org/en/v1/stdlib/Serialization/) warned format would change, but now they have removed the warning, so clearly its stable. Mea Culpa.

best, j

---

<div class="post-metadata">

**Author:** ![dlakelan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dlakelan/32/8491_2.png) [@dlakelan](https://discourse.julialang.org/u/dlakelan)\
**Post date:** [October 14, 2022, 3:26pm UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/36 "2022-10-14T15:26:37Z")

</div>

> [@JackStrauss](#):
>
> The alternative is passing all this data as some gigantic dictionary

Its not hard to avoid globals

```julia
foo = mybigcalc()

while ! done
   dobigcalcaccessingfoo()
end

```

Becomes

```julia
function main()
   foo = mybigcalc()
   done = false
   while ! done
      done = dobigcalc!(foo)
   end
end

main()

```

---

<div class="post-metadata">

**Author:** ![JackStrauss](https://avatars.discourse-cdn.com/v4/letter/j/f14d63/32.png) [@JackStrauss](https://discourse.julialang.org/u/JackStrauss)\
**Post date:** [October 14, 2022, 3:56pm UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/37 "2022-10-14T15:56:45Z")

</div>

> [@dlakelan](#):
>
> Its not hard to avoid globals
> 
> ```julia
> 
> ```

yeah, this works, and I have done that. but if one has several mybigcalc() type functions, they may depend on output from other mybigcalc and a number of functions depend on those calculations, (in our case reporting, feeding dashboard, and sending a sms to admin if urgent error) it becomes doable, but messy.

the globals are anyways global to the module only, not visible outside it.

in python classes one just has self.data which is OK there. c++ has similar.

so, if globals are not to be used, need to be able to share data between many functions somehow.

---

<div class="post-metadata">

**Author:** ![dlakelan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dlakelan/32/8491_2.png) [@dlakelan](https://discourse.julialang.org/u/dlakelan)\
**Post date:** [October 14, 2022, 6:06pm UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/38 "2022-10-14T18:06:10Z")

</div>

> [@JackStrauss](#):
>
> share data between many functions somehow.

Either one struct that you’re passing to all the various functions or depending on if you’re using maybe multiple threads you might use a channel to send information

---

<div class="post-metadata">

**Author:** ![JackStrauss](https://avatars.discourse-cdn.com/v4/letter/j/f14d63/32.png) [@JackStrauss](https://discourse.julialang.org/u/JackStrauss)\
**Post date:** [October 14, 2022, 6:27pm UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/39 "2022-10-14T18:27:21Z")

</div>

> [@dlakelan](#):
>
> Either one struct that you’re passing to all the various functions or depending on if you’re using maybe multiple threads you might use a channel to send information

Yes, we were passing a struct with all relevant data, it makes code more complicated, always receive and pass the same struct. Why just not make it global and simplify code.

All is linear, so no threads in this app (diff on our HPC apps).

But to be honest, I don’t get the dislike of globals. Yes, its horrible to dump all variable into the same namespace, and I value strict scope. I love writing in c `for(int i ; ` , saved me many times. (need to see if julia can do that)

But for sharing data, having one stuct, that can even be called THE\_GLOBAL\_DATA\_STRUCT, why is that wrong?

---

<div class="post-metadata">

**Author:** ![dlakelan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dlakelan/32/8491_2.png) [@dlakelan](https://discourse.julialang.org/u/dlakelan)\
**Post date:** [October 14, 2022, 9:55pm UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/40 "2022-10-14T21:55:20Z")

</div>

> [@JackStrauss](#):
>
> Why just not make it global and simplify code.

You might think it “simplifies” code, but it can absolutely make code harder to understand. At random places throughout the code, the state can change, and you can therefore never know exactly what’s going to happen at any given point. For example:

```julia
if GLOBAL[1] == 3
   something()
   assert(GLOBAL[1] == 3) # wrong... something() changed it
else
   somethingelse()
end

```

Also accessing globals pretty much ensures that you’ll never effectively multithread your code.

Finally, accessing globals is slow in Julia, so it can slow down your entire code base by factors of like 10 easily.

[Previous page](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396.md?page=1)

[Next page](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396.md?page=3)
