# 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:** 1

<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 7, 2022, 11:45am UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/1 "2022-10-07T11:45:50Z")

</div>

I have a function in a module that performs some heavy computation to produce a fixed output.  
It is hard to predict if the function will be called, and we want to avoid computing it if unnecessary.  
So, we would like the function to be lazy and to store its output the first time it is called.  
Here’s one way to do that:

```nohighlight
function f() 
     isdefined(@ __MODULE__ , :_output) && return _output 
     global _output = ... heavy calculations ... 
     return _output 
 end

```

Is it the best and/or most julionic way to do that?

---

<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 7, 2022, 11:53am UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/2 "2022-10-07T11:53:00Z")

</div>

There is a package called `Memoize` (link: [Memoize.jl](https://github.com/JuliaCollections/Memoize.jl)) which does this with simple syntax.

Reusing other packages is considered Julianic.

---

<div class="post-metadata">

**Author:** ![hendri54](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/hendri54/32/9621_2.png) [@hendri54](https://discourse.julialang.org/u/hendri54)\
**Post date:** [October 7, 2022, 12:21pm UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/3 "2022-10-07T12:21:09Z")

</div>

I would define a global `const Output` as a `struct` that is partially initialized at startup.  
When `f` is called, it can check whether the desired field of `Output` has been initialized and compute it if needed.

Making `Output` a functor may be a good implementation (so you can just call `Output()` to retrieve or calculate the desired data as needed). I have not thought through implementation details for this.

---

<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 7, 2022, 2:58pm UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/4 "2022-10-07T14:58:34Z")

</div>

I tried something like this, here is my MWE:

```nohighlight
julia> mutable struct b
       value
       b() = new()
       end

julia> a = b()
b(#undef)

julia> function (f::b)()
             if !isdefined(f,:value)
                 f.value=42
             end
             f.value
       end

julia> a()
42

```

Unfortunately, `!isdefined(f,:value)` always returns true: the functor doesn’t change the struct 😕

---

<div class="post-metadata">

**Author:** ![PetrKryslUCSD](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/petrkryslucsd/32/215825_2.png) [@PetrKryslUCSD](https://discourse.julialang.org/u/PetrKryslUCSD)\
**Post date:** [October 7, 2022, 3:22pm UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/5 "2022-10-07T15:22:28Z")

</div>

I use functors. For instance for transformations where I need a buffer in order to avoid allocations  
in inner loops.

> <https://github.com/PetrKryslUCSD/FinEtoolsFlexStructures.jl/blob/7cd891bbcb980a9ebd4dcd9ef5932544060447fe/src/FEMMShellT3FFModule.jl#L329>

Would this help?

---

<div class="post-metadata">

**Author:** ![ArthurW](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/arthurw/32/19561_2.png) [@ArthurW](https://discourse.julialang.org/u/ArthurW)\
**Post date:** [October 7, 2022, 4:36pm UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/6 "2022-10-07T16:36:47Z")

</div>

You could redefine `f`. It looks a bit better and can be seen as a “Julia thing”.

```julia
julia> function f()
           global _output = (println("heavy calculations"); 12)
           @eval f() = _output
           return _output
       end
f (generic function with 1 method)

julia> f()
heavy calculations
12

julia> f()
12

```

More generally, it seems you want to emulate the behaviour of local static variables from C/C++. There are multiple requests for such feature (see [this issue](https://github.com/JuliaLang/julia/issues/15056)).

---

<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 7, 2022, 4:38pm UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/7 "2022-10-07T16:38:12Z")

</div>

I got some suggestions regarding using structs. Does anybody have a MWE?

In the meantime, would making the side effect more explicit be good?  
Here’s my proposal:

```nohighlight
julia> _f_cache::Union{Nothing,Int} = nothing

julia> function f!(_output = _f_cache)
            _output != nothing && return _output
            global _output = ... heavy calculations ...
            return _output
        end

```

---

<div class="post-metadata">

**Author:** ![stillyslalom](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stillyslalom/32/45687_2.png) [@stillyslalom](https://discourse.julialang.org/u/stillyslalom)\
**Post date:** [October 7, 2022, 4:51pm UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/8 "2022-10-07T16:51:37Z")

</div>

Have you tried Memoize.jl, as previously suggested?

```julia
julia> @memoize f() = sum(rand(1024, 1024)^2)
f (generic function with 1 method)

julia> @time f()
  0.014725 seconds (6 allocations: 16.000 MiB)
2.683692441081925e8

julia> @time f()
  0.000001 seconds
2.683692441081925e8

```

---

<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 7, 2022, 4:56pm UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/9 "2022-10-07T16:56:38Z")

</div>

To fix my MWE on using a functor, I just needed to add the global keyword:

```nohighlight
julia> mutable struct b
       value
       b() = new()
       end

julia> a = b()
b(#undef)

julia> function (f::b)()
             if !isdefined(f,:value)
                 global f.value=42
             end
             f.value
       end

julia> a()
42

```

I think that this is the cleanest approach.

---

<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 7, 2022, 5:00pm UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/10 "2022-10-07T17:00:51Z")

</div>

Yes!  
But I’m curious about achieving it without external packages, since one might want to avoid adding a dependency if the problem has to be solved only in few isolated cases.

---

<div class="post-metadata">

**Author:** ![stillyslalom](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stillyslalom/32/45687_2.png) [@stillyslalom](https://discourse.julialang.org/u/stillyslalom)\
**Post date:** [October 7, 2022, 5:09pm UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/11 "2022-10-07T17:09:59Z")

</div>

I understand the reluctance to add unnecessary dependencies, but Memoize is pretty lightweight:

```julia
julia> @time_imports using Memoize
     19.7 ms MacroTools
      0.2 ms Memoize

```

---

<div class="post-metadata">

**Author:** ![PetrKryslUCSD](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/petrkryslucsd/32/215825_2.png) [@PetrKryslUCSD](https://discourse.julialang.org/u/PetrKryslUCSD)\
**Post date:** [October 7, 2022, 6:28pm UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/12 "2022-10-07T18:28:01Z")

</div>

I think globals are bad. Is there even one example of code that _requires_ a global?

---

<div class="post-metadata">

**Author:** ![ArthurW](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/arthurw/32/19561_2.png) [@ArthurW](https://discourse.julialang.org/u/ArthurW)\
**Post date:** [October 7, 2022, 7:09pm UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/13 "2022-10-07T19:09:21Z")

</div>

It may be worth mentioning that it’s possible to adapt the [redefinition strategy](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/6) to avoid a global variable.

```julia
julia> function f()
           output = (println("heavy calculations"); 12)
           @eval f() = $output
           return output
       end
f (generic function with 1 method)

julia> f()
heavy calculations
12

julia> f()
12

```

---

<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 7, 2022, 9:21pm UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/14 "2022-10-07T21:21:37Z")

</div>

> [@natema](#):
>
> curious about achieving it without external packages,

I think this is a mistake. Memoize does exactly what you want and is debugged and can be applied to whatever you want and works with functions that have multiple arguments etc etc. It’s lightweight and should be considered kind of a core package.

---

<div class="post-metadata">

**Author:** ![PetrKryslUCSD](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/petrkryslucsd/32/215825_2.png) [@PetrKryslUCSD](https://discourse.julialang.org/u/PetrKryslUCSD)\
**Post date:** [October 8, 2022, 12:36am UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/15 "2022-10-08T00:36:01Z")

</div>

Are you sure? It is working for me:

```julia
julia> mutable struct B                                                  
           value                                                         
           B() = new()                                                   
       end                                                               
                                                                         
julia>                                                                   
                                                                         
julia> b = B()                                                           
B(#undef)                                                                
                                                                         
julia>                                                                   
                                                                         
julia> function (b::B)()                                                 
           if !isdefined(b,:value)                                       
               b.value=42                                                
           end                                                           
           b.value                                                       
       end                                                               
                                                                         
julia>                                                                   
                                                                         
julia> @show b()                                                         
b() = 42                                                                 
42                                                                       
                                                                         
julia>                                                                   
                                                                         
julia> b.value = 1                                                       
1                                                                        
                                                                         
julia>                                                                   
                                                                         
julia> @show b()                                                         
b() = 1                                                                  
1                                                                        
                                                                         
julia> 

```

---

<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, 7:36am UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/16 "2022-10-08T07:36:46Z")

</div>

I agree, but in our use case the functions that we want to memoize have no arguments: their aim is to build a fixed object once and for all, if called.  
I confirm also, @PetrKryslUCSD that my favorite solution works, but people argued in private conversations that the struct approach is too verbose.  
I let the thread unsolved atm, so that we can try to achieve some consensus.

---

<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, 9:19am UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/17 "2022-10-08T09:19:52Z")

</div>

To get some practice, I made a macro for this functor solution. It also supports return type specification on the functions. This might improve performance.

```julia
using MacroTools

macro oncefun(ex)

  @capture(shortdef(ex), (fname_() = body_) | (fname_()::T_ = body_)) ||
    error("Need function definition without arguments")

  isnothing(T) && (T = Any)
  sname = gensym()
  ex = quote
    mutable struct $sname
      value::Union{$T,Missing}
      $sname() = new(missing)
    end
    @inline function (ms::$sname)()
      if ismissing(ms.value)
        ms.value = $body
      end
      ms.value::$T
    end
    const $fname = $sname()
  end
  esc(ex)
end

@oncefun f() = (println("run"); 12)

@oncefun function g()::Int
  println("run")
  13
end

```

---

<div class="post-metadata">

**Author:** ![ArthurW](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/arthurw/32/19561_2.png) [@ArthurW](https://discourse.julialang.org/u/ArthurW)\
**Post date:** [October 8, 2022, 10:47am UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/18 "2022-10-08T10:47:09Z")

</div>

> [@dlakelan](#):
>
> and can be applied to whatever you want and works with functions that have multiple arguments etc etc

I think this is the issue. `Memoize.jl` is a great package and would be the clear choice if the point was to do memoization (with more instances, multiple arguments, etc). However, in this case the goal seems to be much simpler: to delay the initialization of a variable until its first use. Achieving this through `@memoize` feels like overkill, as it appears to build an entire (global) dictionary and perform a search everytime you want to access the “lazy variable”. While this should still be “fast enough”, this juggling is sure to confuse the compiler, preventing the use of the “lazy variable” in critical loops (I also suspect it would be a nightmare for compilation to GPUs). More concretly, the `@memoize` approach is very likely to prevent autovectorization.

I believe the best solution would be to have static variables, but, until then, I can see simple cases benefitting from the redefinition trick I proposed. It’s effectively a single extra line and subsequent accesses to the variable should be zero-cost.

In this simple benchmark, the redefinition is something like 7 orders of magnitude faster than memoization:

```julia
using Memoize
using BenchmarkTools

function f()
    val = (println("... heavy calculations ..."); 12)
    @eval f() = $val
    return val
end
@show f()
@show f()

@memoize g() = (println("... heavy calculations ..."); 13)
@show g()
@show g()

function usef()
    s = 0
    for i in 1:1_000_000
        s += i * f() - f()
    end
    return s
end

function useg()
    s = 0
    for i in 1:1_000_000
        s += i * g() - g()
    end
    return s
end

@btime usef()
@btime useg()

```

Output:

```julia
... heavy calculations ...
f() = 12
f() = 12
... heavy calculations ...
g() = 13
g() = 13
  1.490 ns (0 allocations: 0 bytes)
  16.334 ms (0 allocations: 0 bytes)

```

This is due to the compiler confusion I mentioned. The compiler understands what’s going on with `usef()` and optimize everythig away. See the native code:

```julia
        .text
        endbr64
        movabsq $5999994000000, %rax # imm = 0x574FB82D280
        retq
        nop

```

While for `useg()` we get the monstrosity below.

```julia
        .text
        endbr64
        pushq %rbp
        pushq %r15
        pushq %r14
        pushq %r13
        pushq %r12
        pushq %rbx
        subq $72, %rsp
        movabsq $139940655471280, %rcx # imm = 0x7F46790F1AB0
        vxorps %xmm0, %xmm0, %xmm0
        vmovaps %xmm0, 32(%rsp)
        movq $0, 48(%rsp)
        movq %fs:0, %rax
        movq -8(%rax), %rdx
        movq $4, 32(%rsp)
        movq (%rdx), %rax
        movq %rax, 40(%rsp)
        leaq 32(%rsp), %rax
        movq %rdx, 64(%rsp)
        movq %rax, (%rdx)
        movl $1, %ebp
        xorl %ebx, %ebx
        leaq 1787265262(%rcx), %r13
        movabsq $jl_system_image_data, %r14
        movabsq $139942131169320, %r15 # imm = 0x7F46D1047828
        nopl (%rax)
L128:
        movabsq $139940655471280, %rax # imm = 0x7F46790F1AB0
        movq (%rax), %rdi
        movq %rdi, 48(%rsp)
        movq %r14, %rsi
        movq %r15, %rdx
        callq *%r13
        cmpq %r15, %rax
        jne L277
        movabsq $139940626210368, %rax # imm = 0x7F4677509E40
        movq %rax, 8(%rsp)
        movabsq $jl_system_image_data, %rdi
        leaq 8(%rsp), %rsi
        movl $1, %edx
        movabsq $139941898452352, %rax # imm = 0x7F46C3257D80
        callq *%rax
        movabsq $139940655471280, %rax # imm = 0x7F46790F1AB0
        movq %rax, 8(%rsp)
        movabsq $139942130905952, %r12 # imm = 0x7F46D1007360
        movq %r12, 16(%rsp)
        movq %r14, 24(%rsp)
        movabsq $jl_system_image_data, %rdi
        leaq 8(%rsp), %rsi
        movl $3, %edx
        movabsq $139941899165808, %rax # imm = 0x7F46C3306070
        callq *%rax
        movq %r12, %rax
L277:
        movq -8(%rax), %rcx
        shrq $4, %rcx
        movabsq $8746369302984, %rdx # imm = 0x7F46C3C41C8
        cmpq %rdx, %rcx
        jne L570
        movq (%rax), %r12
        imulq %rbp, %r12
        movabsq $139940655471280, %rax # imm = 0x7F46790F1AB0
        movq (%rax), %rdi
        movq %rdi, 48(%rsp)
        movq %r14, %rsi
        movq %r15, %rdx
        callq *%r13
        cmpq %r15, %rax
        jne L494
        movabsq $139940626210368, %rax # imm = 0x7F4677509E40
        movq %rax, 8(%rsp)
        movabsq $jl_system_image_data, %rdi
        movq %r13, %r15
        movq %r14, %r13
        leaq 8(%rsp), %r14
        movq %r14, %rsi
        movl $1, %edx
        movabsq $139941898452352, %rax # imm = 0x7F46C3257D80
        callq *%rax
        movabsq $139940655471280, %rax # imm = 0x7F46790F1AB0
        movq %rax, 8(%rsp)
        movabsq $139942130905952, %rax # imm = 0x7F46D1007360
        movq %rax, 16(%rsp)
        movq %r13, 24(%rsp)
        movabsq $jl_system_image_data, %rdi
        movq %r14, %rsi
        movq %r13, %r14
        movq %r15, %r13
        movabsq $139942131169320, %r15 # imm = 0x7F46D1047828
        movl $3, %edx
        movabsq $139941899165808, %rax # imm = 0x7F46C3306070
        callq *%rax
        movabsq $139942130905952, %rax # imm = 0x7F46D1007360
L494:
        movq -8(%rax), %rcx
        shrq $4, %rcx
        movabsq $8746369302984, %rdx # imm = 0x7F46C3C41C8
        cmpq %rdx, %rcx
        jne L605
        addq %r12, %rbx
        subq (%rax), %rbx
        incq %rbp
        cmpq $1000001, %rbp # imm = 0xF4241
        jne L128
        movq 40(%rsp), %rax
        movq 64(%rsp), %rcx
        movq %rax, (%rcx)
        movq %rbx, %rax
        addq $72, %rsp
        popq %rbx
        popq %r12
        popq %r13
        popq %r14
        popq %r15
        popq %rbp
        retq
L570:
        movabsq $.rodata.str1.1, %rdi
        movabsq $jl_type_error, %rcx
        movabsq $jl_system_image_data, %rsi
        movq %rax, %rdx
        callq *%rcx
L605:
        movabsq $.rodata.str1.1, %rdi
        movabsq $jl_type_error, %rcx
        movabsq $jl_system_image_data, %rsi
        movq %rax, %rdx
        callq *%rcx

```

---

<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, 11:05am UTC](https://discourse.julialang.org/t/how-to-make-a-function-store-data-to-avoid-repeating-computation/88396/19 "2022-10-08T11:05:51Z")

</div>

The eval method only works if you call f() before you call usef(). Otherwise the newly eval-defined f is in a new world age and will not be seen by usef(). Resulting in heavy calculations twice in each loop iteration.

---

<div class="post-metadata">

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

</div>

That’s true.

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