# Understanding allocations when collapsing function arugments

**URL:** https://discourse.julialang.org/t/understanding-allocations-when-collapsing-function-arugments/59668
**Category:** General Usage
**Created:** [April 20, 2021, 2:14pm UTC](https://discourse.julialang.org/t/understanding-allocations-when-collapsing-function-arugments/59668 "2021-04-20T14:14:20Z")
**Posts on this page:** 16
**Page:** 1

<div class="post-metadata">

### Author: ![bashonubuntu](https://avatars.discourse-cdn.com/v4/letter/b/f19dbf/32.png) [@bashonubuntu](https://discourse.julialang.org/u/bashonubuntu)
#### Post date: [April 20, 2021, 2:14pm UTC](https://discourse.julialang.org/t/understanding-allocations-when-collapsing-function-arugments/59668/1 "2021-04-20T14:14:20Z")

</div>

Hi everyone,

I was hoping I could be helped in understanding the following concept. The code is a bit too long to provide a clean MWE, but I am trying to understanding the following:

```julia
julia> @btime integrand_B1_new($y,$params,$ygrid,$W_grid_F1_inv,$result_init)
  808.978 ns (0 allocations: 0 bytes)
0.7020207253704929

fun1(y) = integrand_B1_new(y,params,ygrid,W_grid_F1_inv, result_init) 

julia> @btime fun1($y)
  876.345 ns (2 allocations: 32 bytes)
0.7020207253704929

```

Is there a way to write the collapsed function `fun1(y)` in a way such that it does not allocate? I make calls to fun1(y) in a function like `quadgk()` to interpolate some intergrals over a grid. For example, I use `quadgk(fun1, min, max)` and over a few hundred (or thousand) points over the grid, the calls to `fun1(y)` add up quite a bit.

Conceptually understanding what may be going on would be the most helpful.

---

<div class="post-metadata">

### Author: ![dpsanders](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dpsanders/32/3573_2.png) [@dpsanders](https://discourse.julialang.org/u/dpsanders)
#### Post date: [April 20, 2021, 2:25pm UTC](https://discourse.julialang.org/t/understanding-allocations-when-collapsing-function-arugments/59668/2 "2021-04-20T14:25:53Z")

</div>

You are using global variables inside fun1. You always want to avoid doing that. One way is to use a closure instead.

---

<div class="post-metadata">

### Author: ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)
#### Post date: [April 20, 2021, 2:27pm UTC](https://discourse.julialang.org/t/understanding-allocations-when-collapsing-function-arugments/59668/3 "2021-04-20T14:27:54Z")

</div>

What you are doing there is using a lot of global variables inside `fun1`. You are doing something like:

```julia
julia> g(x,y) = x + y
g (generic function with 1 method)

julia> function f1(x)
         g(x,y)
       end
f1 (generic function with 1 method)

```

and `y` is a global variable inside `f1 `. That would be ok if `y` was a `const` (constant global), but does not seem to be the case. That is very bad for performance because the type of `y` is not stable.

You can use a closure, for example:

```julia
julia> function f2(x, inner_g)
            inner_g(x)
       end
f2 (generic function with 1 method)

julia> f2(1, x -> g(x,2))
3

```

Or look at this thread: [Define data-dependent function, various ways](https://discourse.julialang.org/t/define-data-dependent-function-various-ways/48988) for more alternatives.

---

<div class="post-metadata">

### Author: ![bashonubuntu](https://avatars.discourse-cdn.com/v4/letter/b/f19dbf/32.png) [@bashonubuntu](https://discourse.julialang.org/u/bashonubuntu)
#### Post date: [April 20, 2021, 2:30pm UTC](https://discourse.julialang.org/t/understanding-allocations-when-collapsing-function-arugments/59668/4 "2021-04-20T14:30:30Z")

</div>

Thanks @dpsanders! Does it matter that I am using `fun1()` inside `main()` and within main() I pass `params,ygrid,W_grid_F1_inv, result_init` as arguments. Would the globals still be an issue?

The allocation issue persists even when timed inside `main().`

---

<div class="post-metadata">

### Author: ![bashonubuntu](https://avatars.discourse-cdn.com/v4/letter/b/f19dbf/32.png) [@bashonubuntu](https://discourse.julialang.org/u/bashonubuntu)
#### Post date: [April 20, 2021, 2:32pm UTC](https://discourse.julialang.org/t/understanding-allocations-when-collapsing-function-arugments/59668/5 "2021-04-20T14:32:36Z")

</div>

> [@lmiq](#):
>
> `f2(1, x -> g(x,2))`

Thanks @lmiq. Let me try your suggestion and get back to you. This is what I was writing to @dpsanders (to request an example), but you beat me to the punch 🙂

---

<div class="post-metadata">

### Author: ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)
#### Post date: [April 20, 2021, 2:42pm UTC](https://discourse.julialang.org/t/understanding-allocations-when-collapsing-function-arugments/59668/6 "2021-04-20T14:42:54Z")

</div>

> [@bashonubuntu](#):
>
> The allocation issue persists even when timed inside `main()`

Yes, this is a tricky thing. I guess you mean something like:

```julia
julia> function main(x)
         g(x,z) = x + z
         f(x) = g(x,y)
         y = 2
         f(x)
       end
main (generic function with 1 method)

julia> @btime main(1)
  20.894 ns (1 allocation: 16 bytes)
3

```

This is confusing and there are other discussion whether that should raise an error, a warning, or something else. The problem is that there is an ambiguity on whether `y` is a constant in the local scope, if it can vary, and that apparently is making the compiler make bad choices (outside my scope of understanding).

The safe alternative is being more explicit on what one wants:

```julia
julia> function main(x)
         g(x,y) = x + y
         f(x,inner_g) = inner_g(x)
         y = 2
         f(x, x -> g(x,y))
       end
main (generic function with 1 method)

julia> @btime main(1)
  0.025 ns (0 allocations: 0 bytes)
3

```

But with a lot of different parameters, I would play even safer and use a struct:

```julia
julia> struct Params
         y::Int
       end
       function main(x)
         g(x,y) = x + y 
         f(x,p) = g(x,p.y)
         p = Params(2)
         f(x,p)
       end
main (generic function with 1 method)

julia> @btime main(1)
  0.023 ns (0 allocations: 0 bytes)
3

```

I just opened another thread with a related issue: [Performance closures with changing value of closed over variables](https://discourse.julialang.org/t/performance-closures-with-changing-value-of-closed-over-variables/59671)

---

<div class="post-metadata">

### Author: ![bashonubuntu](https://avatars.discourse-cdn.com/v4/letter/b/f19dbf/32.png) [@bashonubuntu](https://discourse.julialang.org/u/bashonubuntu)
#### Post date: [April 20, 2021, 2:47pm UTC](https://discourse.julialang.org/t/understanding-allocations-when-collapsing-function-arugments/59668/7 "2021-04-20T14:47:36Z")

</div>

> [@lmiq](#):
>
> ```julia
> julia> function main(x)
> g(x,z) = x + z
> f(x) = g(x,y)
> y = 2
> f(x)
> end
> main (generic function with 1 method)
> 
> julia> @btime main(1)
> 20.894 ns (1 allocation: 16 bytes)
> 3
> 
> ```

Yes, this is exactly my use case. And, the reason I need to do an argument collapse is because I eventually want to use `quadgk` which needs a one function argument. Thank you again for the amazingly detailed responses. Let me study these, try and I will get back to you here.

---

<div class="post-metadata">

### Author: ![bashonubuntu](https://avatars.discourse-cdn.com/v4/letter/b/f19dbf/32.png) [@bashonubuntu](https://discourse.julialang.org/u/bashonubuntu)
#### Post date: [April 20, 2021, 5:16pm UTC](https://discourse.julialang.org/t/understanding-allocations-when-collapsing-function-arugments/59668/8 "2021-04-20T17:16:20Z")

</div>

> [@lmiq](#):
>
> `f(x,inner_g) = inner_g(x)`

Thanks for your suggestions! I made some progress, but not completely I think.

Here is what I was doing previously

```julia
fun1(y) = integrand_B1_new(y,params,ygrid,W_grid_F1_inv, result_init) 

```

Here is what I changed. Does this seem right?

```julia
function fun1_new(y::F, params::P,ygrid::Array{F},
                  W_grid_F1_inv::Array{F}, result_init::Array{S}, 
                  integrand_B1_new::Fxn) where {F<:Float64, S<:Int, P<:Param, Fxn}

    g(y, params,ygrid,W_grid_F1_inv, result_init, integrand_B1_new) =
      integrand_B1_new(y)

    g(y, params,ygrid,W_grid_F1_inv, result_init, 
      y -> integrand_B1_new(y,params,ygrid,W_grid_F1_inv,result_init))

end

```

I ran these two functions inside `main(...., params, ygrid, W_grid_F1_inv, result_init, integrand_B1_new, .....)` like so

```julia

function main(...., params, ygrid, W_grid_F1_inv, result_init, 
              integrand_B1_new, .....)

.
.
.

@timeit to "Quad GK new" quadgk(y -> fun1_new(y, params, ygrid, 
W_grid_F1_inv, result_init, integrand_B1_new),0.0,F2_grid[1])[1]
        
@timeit to "Quad GK old" quadgk(fun1,0.0,F2_grid[1])[1]

.
.
.

end

```

I got the following result

```julia
 Section ncalls time %tot avg alloc %tot avg
 ──────────────────────────────────────────────────────────────────────
 Quad GK old 1 69.0μs 71.4% 69.0μs 1.59KiB 82.3% 1.59KiB
 Quad GK new 1 27.6μs 28.6% 27.6μs 352B 17.7% 352B

```

The allocations reduced quite a bit although not completely gone. As mentioned, `integrand_B_new()` does not allocate because

```julia
@btime integrand_B1_new($y,$params,$ygrid,$W_grid_F1_inv,$result_init)
  808.978 ns (0 allocations: 0 bytes)
0.7020207253704929

```

Would you have any other suggestions?

---

<div class="post-metadata">

### Author: ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)
#### Post date: [April 20, 2021, 5:27pm UTC](https://discourse.julialang.org/t/understanding-allocations-when-collapsing-function-arugments/59668/9 "2021-04-20T17:27:45Z")

</div>

Uhm… I think it would be better if you provided a minimal complete running example. I don’t understand exactly what you are doing there now.

---

<div class="post-metadata">

### Author: ![bashonubuntu](https://avatars.discourse-cdn.com/v4/letter/b/f19dbf/32.png) [@bashonubuntu](https://discourse.julialang.org/u/bashonubuntu)
#### Post date: [April 20, 2021, 5:28pm UTC](https://discourse.julialang.org/t/understanding-allocations-when-collapsing-function-arugments/59668/10 "2021-04-20T17:28:55Z")

</div>

Might be a bit hard but let me try to wing something. That way you can run on your end as well.

---

<div class="post-metadata">

### Author: ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)
#### Post date: [April 20, 2021, 5:57pm UTC](https://discourse.julialang.org/t/understanding-allocations-when-collapsing-function-arugments/59668/11 "2021-04-20T17:57:56Z")

</div>

But the general pattern is, for example:

```julia
Base.@kwdef struct P
  a::Float64
  b::Float64
  xmin::Float64
  xmax::Float64
  rtol:Float64
end

f(p) = quadgk(x -> exp(-p.a*x^p.b), p.xmin, p.xmax, rtol=p.rtol)

p = P(a=1.0, b=2.0, xmin=0.0, xmax=1.0, rtol=1e-8)

julia> @btime f($p)
  448.594 ns (13 allocations: 288 bytes)
(0.746824132812427, 7.887024366937112e-13)

```

---

<div class="post-metadata">

### Author: ![bashonubuntu](https://avatars.discourse-cdn.com/v4/letter/b/f19dbf/32.png) [@bashonubuntu](https://discourse.julialang.org/u/bashonubuntu)
#### Post date: [April 20, 2021, 6:15pm UTC](https://discourse.julialang.org/t/understanding-allocations-when-collapsing-function-arugments/59668/12 "2021-04-20T18:15:51Z")

</div>

@leandromartinez98 Since you asked for an MWE, here is the closest example I think. I am wondering if there is a way to get the allocations showing up in `fun2()` which is used by `quadgk()` inside `main()` to 0.

```julia

using QuadGK
using TimerOutputs

x = 3.0;
z = 2.0;
const to = TimerOutput();

function integrand_example(x, z) 
     
     x^2 + z  
    
end

fun1(x) = integrand_example(x, z)

function fun2(x, z, integrand_example)
  g(x, z, integrand_example) = integrand_example(x)
  g(x, z, x -> integrand_example(x, z))
end

function main(x, z, integrand_example)

    @timeit to "Quad GK new" quadgk(x -> fun2(x, z, integrand_example),0.0,0.6)[1]
    @timeit to "Quad GK old" quadgk(fun1,0.0,0.6)[1]

end

julia> @btime integrand_example($x, $z)
  0.001 ns (0 allocations: 0 bytes)
11.0

julia> @btime fun2($x, $z, $integrand_example)
  0.001 ns (0 allocations: 0 bytes)
11.0

main(x, z, integrand_example)
show(to, allocations = true, compact = false, sortby = :allocations)

                               Time Allocations
                       ────────────────────── ───────────────────────
   Tot / % measured: 5.38s / 0.00% 825KiB / 0.21%

 Section ncalls time %tot avg alloc %tot avg
 ──────────────────────────────────────────────────────────────────────
 Quad GK old 1 47.0μs 82.3% 47.0μs 1.61KiB 93.6% 1.61KiB
 Quad GK new 1 10.1μs 17.7% 10.1μs 112B 6.36% 112B

```

---

<div class="post-metadata">

### Author: ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)
#### Post date: [April 20, 2021, 6:35pm UTC](https://discourse.julialang.org/t/understanding-allocations-when-collapsing-function-arugments/59668/13 "2021-04-20T18:35:37Z")

</div>

To be truth, I do not understand what exactly that benchmark is reporting. Does not seem to make sense to me. Also your definitions of `g` there are redudant. A minimal example of the two approaches would be, I think:

```julia
using QuadGK
using TimerOutputs

function integrand_example(x, z)
     x^2 + z
end

#old
fun1(x) = integrand_example(x, z)
main_old() = quadgk(fun1,0.0,0.6)[1]

#new
main_new(x,z) = quadgk(x -> integrand_example(x,z),0.0,0.6)[1]

x = 3.0
z = 2.0

using BenchmarkTools

print("old: "); @btime main_old()

print("new: "); @btime main_new($x,$z)

```

```julia
julia> include("./bash.jl")
old: 1.406 μs (90 allocations: 1.61 KiB)
new: 256.691 ns (8 allocations: 176 bytes)
1.272

```

The problem with the “old” approach is more explicit if you write:

```julia
main_old() = quadgk(x -> fun1(x,z),0.0,0.6)[1]

```

`x` is the variable of integration (that is fine). But `z` is a parameter which is, in this case, a global, non-constant variable (if you declare `const z = 2.0` insted, the difference goes away).

---

<div class="post-metadata">

### Author: ![bashonubuntu](https://avatars.discourse-cdn.com/v4/letter/b/f19dbf/32.png) [@bashonubuntu](https://discourse.julialang.org/u/bashonubuntu)
#### Post date: [April 20, 2021, 6:49pm UTC](https://discourse.julialang.org/t/understanding-allocations-when-collapsing-function-arugments/59668/14 "2021-04-20T18:49:40Z")

</div>

> [@lmiq](#):
>
> `main_new(x,z)`

Ah great, yes now I understand. So, it seems like what we are measuring then as 112B/176B is some overhead for `quadgk()`. Would that be correct? Admittedly, I’m not the TimerOutputs expert here 🙂

Also, I think @stevengj would know better if quadgk() always allocates. I think it does based on what we just did, but I’m purely speculating.

---

<div class="post-metadata">

### Author: ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)
#### Post date: [April 20, 2021, 7:19pm UTC](https://discourse.julialang.org/t/understanding-allocations-when-collapsing-function-arugments/59668/15 "2021-04-20T19:19:36Z")

</div>

> [@bashonubuntu](#):
>
> 112B/176B is some overhead for `quadgk()` . Would that be correct?

No, no, actually you are measuring compilation time there, because you are benchmarking the first call…

You should do:

```julia
const to = TimerOutput()
function main(x,z)
  x = 3.0
  z = 2.0
  @timeit to "old" main_old()
  @timeit to "new" main_new(x,z)
end
main(x,z)
reset_timer!(to)
main(x,z)
show(to, allocations = true, compact = false, sortby = :allocations)

```

With which you get:

```julia
 ──────────────────────────────────────────────────────────────────
                           Time Allocations
                   ────────────────────── ───────────────────────
 Tot / % measured: 499ms / 0.00% 34.9MiB / 0.00%

 Section ncalls time %tot avg alloc %tot avg
 ──────────────────────────────────────────────────────────────────
 old 1 20.2μs 84.4% 20.2μs 1.61KiB 90.4% 1.61KiB
 new 1 3.73μs 15.6% 3.73μs 176B 9.65% 176B
 ──────────────────────────────────────────────────────────────────

```

Still I do not understand why the times differ so much from the use of `@btime`, but at least the allocations and trend make sense now.

---

<div class="post-metadata">

### Author: ![bashonubuntu](https://avatars.discourse-cdn.com/v4/letter/b/f19dbf/32.png) [@bashonubuntu](https://discourse.julialang.org/u/bashonubuntu)
#### Post date: [April 20, 2021, 7:24pm UTC](https://discourse.julialang.org/t/understanding-allocations-when-collapsing-function-arugments/59668/16 "2021-04-20T19:24:25Z")

</div>

Actually I had skipped timing the first call in what I posted and only showed you the second run (after doing a reset()).

Yes, I’m also a bit puzzled why the timings are different. Let me also check the docs.
