# How can I eliminate allocations here?

**URL:** <https://discourse.julialang.org/t/how-can-i-eliminate-allocations-here/68859>\
**Category:** Performance\
**Tags:** memory-allocation, allocations\
**Created:** [September 28, 2021, 9:01am UTC](https://discourse.julialang.org/t/how-can-i-eliminate-allocations-here/68859 "2021-09-28T09:01:32Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![amrods](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/amrods/32/2543_2.png) [@amrods](https://discourse.julialang.org/u/amrods)\
**Post date:** [September 28, 2021, 9:01am UTC](https://discourse.julialang.org/t/how-can-i-eliminate-allocations-here/68859/1 "2021-09-28T09:01:32Z")

</div>

How can I eliminate allocations here? I understand I am allocating in `shares1 = copy(shares)`, but where else?

```julia
function logit(x; min=0, max=1)
    (max - min) * (1/(1 + exp(-x))) + min
end

function _cessum(x, shares, r)
    n = length(x)
    (n == length(shares)) || throw(DimensionMismatch())
    s = zero(eltype(x))
    for i in 1:n
        s += shares[i]*x[i]^r
    end
    return s
end

function ces(x, shares, r; normfun=logit)
    n = length(x)
    shares1 = copy(shares)
    sharessum = zero(eltype(shares))
    for i in 1:n
        shares1[i] = normfun(shares1[i]; max=1-sharessum)
        sharessum += shares1[i]
    end
    return _cessum(x, shares1, r)^(1/r)
end

n = 5
x = rand(n)
shares = rand(n)
r = 0.3

using BenchmarkTools

ulia> @benchmark ces($x, $shares, $r; normfun=logit)
BenchmarkTools.Trial: 10000 samples with 180 evaluations.
 Range (min … max): 571.317 ns … 56.049 μs ┊ GC (min … max): 0.00% … 98.56%
 Time (median): 595.050 ns ┊ GC (median): 0.00%
 Time (mean ± σ): 661.777 ns ± 1.414 μs ┊ GC (mean ± σ): 5.62% ± 2.60%

  ▇▆█▆▄▁ ▁ ▁
  ██████████████▇█▇▇▇█▇█▇▇██▇▇▆▆▇▆▆▆▇▆▅▇▆▆▆▆▅▅▆▆▆▆▅▄▄▅▅▃▅▅▄▅▅▅ █
  571 ns Histogram: log(frequency) by time 1.15 μs <

 Memory estimate: 368 bytes, allocs estimate: 16.

```

---

<div class="post-metadata">

**Author:** ![Karajan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/karajan/32/8545_2.png) [@Karajan](https://discourse.julialang.org/u/Karajan)\
**Post date:** [September 28, 2021, 1:29pm UTC](https://discourse.julialang.org/t/how-can-i-eliminate-allocations-here/68859/2 "2021-09-28T13:29:42Z")

</div>

So the problem seems to be the keyword arguments in the `logit` function. If you replace

```julia
logit(x; min=0, max=1) -> logit(x, min, max)
normfun(shares1[i]; max=1-sharessum) -> normfun(shares1[i], 0.0, 1-sharessum)

```

the allocations go away (and it’s way faster 👍).  
Unfortunately I can’t really tell you why this is the case, maybe someone else can chime in.

---

<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:** [September 28, 2021, 1:48pm UTC](https://discourse.julialang.org/t/how-can-i-eliminate-allocations-here/68859/3 "2021-09-28T13:48:39Z")

</div>

It is something related to the fact that the function does not specialize to keyword arguments, and neither to the input function. For example:

```julia
julia> function ces(x, shares, shares1, r, normfun::F) where F
           n = length(x)
           #shares1 = copy(shares)
           sharessum = zero(eltype(shares))
           for i in 1:n
               shares1[i] = normfun(shares1[i]; max=1-sharessum)
               sharessum += shares1[i]
           end
           return _cessum(x, shares1, r)^(1/r)
       end
ces (generic function with 3 methods)

julia> @benchmark ces($x, $shares, $shares1, $r, $logit)
BenchmarkTools.Trial: 10000 samples with 196 evaluations.
 Range (min … max): 476.944 ns … 1.468 μs ┊ GC (min … max): 0.00% … 0.00%
 Time (median): 477.663 ns ┊ GC (median): 0.00%
 Time (mean ± σ): 488.912 ns ± 47.475 ns ┊ GC (mean ± σ): 0.00% ± 0.00%

  █ ▂ ▂ ▂▁ ▁ ▁
  █▃█▆▅▅▅█▁▄██▁▄▅▆▁▃▄▆▅▆▆█▄▄▅▄▇▆▄▅▃█▄▄▃▃▅▃▄▄▃▄▇▃▅▆▄▄█▄▁▄▄▅▅█▄▅ █
  477 ns Histogram: log(frequency) by time 692 ns <

 Memory estimate: 0 bytes, allocs estimate: 0.

```

(I moved `shares1` to a parameter to eliminate all allocations).

Probably related to this: [Performance Tips · The Julia Language](https://docs.julialang.org/en/v1/manual/performance-tips/#Be-aware-of-when-Julia-avoids-specializing)

---

<div class="post-metadata">

**Author:** ![amrods](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/amrods/32/2543_2.png) [@amrods](https://discourse.julialang.org/u/amrods)\
**Post date:** [September 28, 2021, 10:22pm UTC](https://discourse.julialang.org/t/how-can-i-eliminate-allocations-here/68859/4 "2021-09-28T22:22:55Z")

</div>

I modified `ces` like below:

```julia
function ces(x, shares, r; normfun::F=logit, shares1=copy(shares)) where F
    n = length(x)
    sharessum = zero(eltype(shares))
    for i in 1:n
        shares1[i] = normfun(shares1[i]; max=1-sharessum)
        sharessum += shares1[i]
    end
    return _cessum(x, shares1, r)^(1/r)
end

shares1 = copy(shares)

julia> @benchmark ces($x, $shares, $r; normfun=$logit, shares1=$shares1)
BenchmarkTools.Trial: 10000 samples with 846 evaluations.
 Range (min … max): 140.082 ns … 397.773 ns ┊ GC (min … max): 0.00% … 0.00%
 Time (median): 150.333 ns ┊ GC (median): 0.00%
 Time (mean ± σ): 155.629 ns ± 26.517 ns ┊ GC (mean ± σ): 0.00% ± 0.00%

  ▅█ ▅█▃▅▁ ▁ ▁ ▁ ▂
  ██▇▁█████████▇█▇█▆██▇█▇█▆▇█▇▇█▇▆█▇▆██▇▇▇▇▆▆▇▆▇▆▆▆▆▆▅▆▆▆▆▅▆▆▆▆ █
  140 ns Histogram: log(frequency) by time 280 ns <

 Memory estimate: 0 bytes, allocs estimate: 0.

```

Aside, I thought specifying types like `f(x::T) where T` was the same as `f(x::Any)`. Is that not the case?

---

<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:** [September 29, 2021, 12:31am UTC](https://discourse.julialang.org/t/how-can-i-eliminate-allocations-here/68859/5 "2021-09-29T00:31:28Z")

</div>

> [@amrods](#):
>
> Aside, I thought specifying types like `f(x::T) where T` was the same as `f(x::Any)` . Is that not the case?

In general it is, for the specific case of function it might not be (each function has its own type, so they are special in that sense). Also, there is the exception where that declaration serves as a signal for the compiler to specialize the function to that type of function (but that only should make a different if the function in case is _not_ used, but only passed as an argument, inside the function). Thus, I do not exactly understand what is the case here, I think is a combination of the function argument and optional arguments. Someone that understands these details better will probably be able to explain this better.

@amrods add on: I think is sort of an anti-pattern also to pass a function as a parameter but use keyword arguments inside the called function. Keyword arguments are specific to the function used, so it does not match well with the flexibility given the function being a parameter.

I would suggest a pattern like this: in the inner function, decide how many parameters the function has to receive (always). Then, you change the function being passed by the caller by providing a closure:

```julia
julia> function ces(x, shares, shares1, r; normfun=logit)
           n = length(x)
           #shares1 = copy(shares)
           sharessum = zero(eltype(shares))
           for i in 1:n
               shares1[i] = normfun(shares1[i],1-sharessum)
               sharessum += shares1[i]
           end
           return _cessum(x, shares1, r)^(1/r)
       end
ces (generic function with 2 methods)

julia> @benchmark ces($x, $shares, $shares1, $r; normfun=(x,max) -> logit(x,max=max))
BenchmarkTools.Trial: 10000 samples with 195 evaluations.
 Range (min … max): 487.031 ns … 2.310 μs ┊ GC (min … max): 0.00% … 0.00%
 Time (median): 496.036 ns ┊ GC (median): 0.00%
 Time (mean ± σ): 564.994 ns ± 132.099 ns ┊ GC (mean ± σ): 0.00% ± 0.00%

  █▂▃▄▁▃▃▂▁ ▁ ▁ ▁ ▁▄▁ ▁ ▁
  ███████████▆▇▆███▆▇███▇▇█▇▇████▇▇██▇▆██▇▅▆▅▆▅▅▅▅▅▆▄▅▃▂▃▄▅▄▄▃▃ █
  487 ns Histogram: log(frequency) by time 1.05 μs <

 Memory estimate: 0 bytes, allocs estimate: 0.

```

With this, you disassociate the calling syntax of the function being passed from the calling syntax internally.

(this also happens to solve the allocation issue).

---

<div class="post-metadata">

**Author:** ![amrods](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/amrods/32/2543_2.png) [@amrods](https://discourse.julialang.org/u/amrods)\
**Post date:** [September 29, 2021, 12:28pm UTC](https://discourse.julialang.org/t/how-can-i-eliminate-allocations-here/68859/6 "2021-09-29T12:28:15Z")

</div>

I use keywords all over my code. It’s turtles all the way down!
