# 10x slowdown when passing function as argument

**URL:** <https://discourse.julialang.org/t/10x-slowdown-when-passing-function-as-argument/40382>\
**Category:** Performance\
**Created:** [May 28, 2020, 11:39pm UTC](https://discourse.julialang.org/t/10x-slowdown-when-passing-function-as-argument/40382 "2020-05-28T23:39:59Z")\
**Posts on this page:** 16\
**Page:** 1

<div class="post-metadata">

**Author:** ![jlchan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jlchan/32/10958_2.png) [@jlchan](https://discourse.julialang.org/u/jlchan)\
**Post date:** [May 28, 2020, 11:39pm UTC](https://discourse.julialang.org/t/10x-slowdown-when-passing-function-as-argument/40382/1 "2020-05-28T23:39:59Z")

</div>

I’m trying to understand why passing a function as an argument is sometimes 10x slower than inheriting it via closure. Here’s a contrived MWE:

```julia
function outer1(f)
    out = zeros(1000)
    for i = 1:1000
        function inner(x)
            return f(2*x)
        end
        out[i] = inner(i)
    end
end

function outer2(f)
    out = zeros(1000)
    for i = 1:1000
        function inner(x,f)
            return f(2*x)
        end
        out[i] = inner(i,f)
    end
end

f(x) = exp(x)

@btime outer1($f)
@btime outer2($f)

```

The output is

```julia
  4.399 μs (1 allocation: 7.94 KiB)
  42.277 μs (1979 allocations: 38.84 KiB)

```

Where are all the extra allocations (which I assume are the reason for the slowdown) coming from?

---

<div class="post-metadata">

**Author:** ![Oscar\_Smith](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/oscar_smith/32/25343_2.png) [@Oscar\_Smith](https://discourse.julialang.org/u/Oscar_Smith)\
**Post date:** [May 28, 2020, 11:46pm UTC](https://discourse.julialang.org/t/10x-slowdown-when-passing-function-as-argument/40382/2 "2020-05-28T23:46:07Z")

</div>

The problem is that functions are automatically not specialized. You want `function inner(x,f::F) where F` Also, I would definitely recommend not defining the function in a loop.

---

<div class="post-metadata">

**Author:** ![jlchan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jlchan/32/10958_2.png) [@jlchan](https://discourse.julialang.org/u/jlchan)\
**Post date:** [May 28, 2020, 11:52pm UTC](https://discourse.julialang.org/t/10x-slowdown-when-passing-function-as-argument/40382/3 "2020-05-28T23:52:37Z")

</div>

I think I actually want `outer2(f::F) where F` - this fixes the issue, and the runtimes are about equal now.

For this MWE, I don’t see any difference in performance based on where I define the function. Why is it not recommended in general?

---

<div class="post-metadata">

**Author:** ![kevinczimmerman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kevinczimmerman/32/13040_2.png) [@kevinczimmerman](https://discourse.julialang.org/u/kevinczimmerman)\
**Post date:** [May 29, 2020, 12:00am UTC](https://discourse.julialang.org/t/10x-slowdown-when-passing-function-as-argument/40382/4 "2020-05-29T00:00:27Z")

</div>

> [@jlchan](#):
>
> Why is it not recommended in general?

The function is independent of the loop iteration.

---

<div class="post-metadata">

**Author:** ![jlchan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jlchan/32/10958_2.png) [@jlchan](https://discourse.julialang.org/u/jlchan)\
**Post date:** [May 29, 2020, 12:12am UTC](https://discourse.julialang.org/t/10x-slowdown-when-passing-function-as-argument/40382/5 "2020-05-29T00:12:38Z")

</div>

Yes, I realize it’s unnecessary to define it in the loop, but I’d like to understand specifically why it’s bad. e.g.,

- is it because it’s ugly or hard to read?
- is it b/c there are possible performance losses?

I’m not trying to argue that it’s good practice, but it would help me understand Julia better if I understood the consequences of doing dumb stuff like defining functions in loops.

---

<div class="post-metadata">

**Author:** ![kevinczimmerman](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kevinczimmerman/32/13040_2.png) [@kevinczimmerman](https://discourse.julialang.org/u/kevinczimmerman)\
**Post date:** [May 29, 2020, 12:14am UTC](https://discourse.julialang.org/t/10x-slowdown-when-passing-function-as-argument/40382/6 "2020-05-29T00:14:31Z")

</div>

I understand what you’re saying and I think it’s a good question to have answered. The 2 cents I gave was the only reason I could sufficiently articulate. I’ll leave it to those smarter than I to answer your latest inquiry.

---

<div class="post-metadata">

**Author:** ![jlchan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jlchan/32/10958_2.png) [@jlchan](https://discourse.julialang.org/u/jlchan)\
**Post date:** [May 29, 2020, 12:40am UTC](https://discourse.julialang.org/t/10x-slowdown-when-passing-function-as-argument/40382/7 "2020-05-29T00:40:48Z")

</div>

Turns out that (yet again) the answer was lying in plain sight in the [Performance Tips](https://docs.julialang.org/en/v1/manual/performance-tips/index.html#Be-aware-of-when-Julia-avoids-specializing-1).

> Julia avoids automatically specializing on argument type parameters in three specific cases: `Type` , `Function` , and `Vararg` . Julia will always specialize when the argument is used within the method, **but not if the argument is just passed through to another function**.
> 
> […] If you find it does have a performance impact at runtime in your case, you can trigger specialization by adding a type parameter to the method declaration.

---

<div class="post-metadata">

**Author:** ![ettersi](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ettersi/32/6829_2.png) [@ettersi](https://discourse.julialang.org/u/ettersi)\
**Post date:** [May 29, 2020, 1:22am UTC](https://discourse.julialang.org/t/10x-slowdown-when-passing-function-as-argument/40382/8 "2020-05-29T01:22:49Z")

</div>

Not that anyone asked me, but I think that these rules regarding specialisation of function arguments are absolutely horrible. It’s just too unintuitive why `outer1()` would be any different from `outer2()`. I assume the implicit assumption is that if `fun(f::Function)` merely passes `f`, then most likely it is a short function which is inlined and hence the lack of specialisation disappears?

**Edit:** Obviously the above remark is not intended to insult anyone or to claim that I knew how to do it better.

---

<div class="post-metadata">

**Author:** ![jlchan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jlchan/32/10958_2.png) [@jlchan](https://discourse.julialang.org/u/jlchan)\
**Post date:** [May 29, 2020, 2:06am UTC](https://discourse.julialang.org/t/10x-slowdown-when-passing-function-as-argument/40382/10 "2020-05-29T02:06:12Z")

</div>

It does feel a little opaque to me, like I’m guessing at Julia’s behavior when it comes to passing functions. I was able to speed up my actual code by adding type specification, but it slowed back down 10x after I moved the functions out of the for loop in which they were defined 🙄.

Trying to reproduce it in a MWE again…

---

<div class="post-metadata">

**Author:** ![jlchan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jlchan/32/10958_2.png) [@jlchan](https://discourse.julialang.org/u/jlchan)\
**Post date:** [May 29, 2020, 3:53am UTC](https://discourse.julialang.org/t/10x-slowdown-when-passing-function-as-argument/40382/11 "2020-05-29T03:53:42Z")

</div>

I can’t seem to reproduce the error where performance changed after moving the functions out of the loop, but I’m still having understanding function passing performance.

I can fix the slowdown in the original post by type specifying (I don’t need to type specify inner(…) but doing so doesn’t reduce performance).

```julia
function outer2(f::Fxn) where Fxn
    function inner(x,f::Fxn) where Fxn
        return f(2*x)
    end
    out = zeros(1000)
    for i = 1:1000
        out[i] = inner(i,f)
    end
end

```

However, if I pass the function thru twice, I can’t seem to avoid slowdown via type specification.

```julia
function outer3(f::Fxn) where Fxn
    function pass(x,f::Fxn) where Fxn
        return inner(x,f)
    end
    function inner(x,f::Fxn) where Fxn
        return f(2*x)
    end

    out = zeros(1000)
    for i = 1:1000
        out[i] = pass(i,f)
    end
end

f(x) = exp(x)

```

Timing both gives

```julia
  4.226 μs (1 allocation: 7.94 KiB) # outer2
  50.668 μs (3979 allocations: 70.09 KiB) # outer3

```

Any suggestions?

---

<div class="post-metadata">

**Author:** ![ettersi](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ettersi/32/6829_2.png) [@ettersi](https://discourse.julialang.org/u/ettersi)\
**Post date:** [May 29, 2020, 5:35am UTC](https://discourse.julialang.org/t/10x-slowdown-when-passing-function-as-argument/40382/12 "2020-05-29T05:35:55Z")

</div>

Not to define `pass` and `inner` inside `outer` seems to help:

```julia
julia> function pass(x,f::Fxn) where Fxn
           return inner(x,f)
       end
       function inner(x,f::Fxn) where Fxn
           return f(2*x)
       end

       function outer4(f::Fxn) where Fxn
           out = zeros(1000)
           for i = 1:1000
               out[i] = pass(i,f)
           end
       end

       @btime outer4(exp)
  5.052 μs (1 allocation: 7.94 KiB)

```

If you are worried about “leaking” `inner` and `pass` into the surrounding scope, you could move them into a module `outer_utils`.

---

<div class="post-metadata">

**Author:** ![Seif\_Shebl](https://avatars.discourse-cdn.com/v4/letter/s/eada6e/32.png) [@Seif\_Shebl](https://discourse.julialang.org/u/Seif_Shebl)\
**Post date:** [May 31, 2020, 9:19am UTC](https://discourse.julialang.org/t/10x-slowdown-when-passing-function-as-argument/40382/13 "2020-05-31T09:19:39Z")

</div>

You have to anotate only the outermost function, and define functions before they are called:

```julia
function outer3(f::Fxn) where {Fxn}
    function inner(x,f)
        return f(2*x)
    end
    function pass(x,f)
        return inner(x,f)
    end
    out = zeros(1000)
    for i = 1:1000
        out[i] = pass(i,f)
    end
    out 
end

using BenchmarkTools
f(x) = exp(x)
@btime outer3(f)
  4.967 μs (1 allocation: 7.94 KiB)

```

---

<div class="post-metadata">

**Author:** ![ettersi](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/ettersi/32/6829_2.png) [@ettersi](https://discourse.julialang.org/u/ettersi)\
**Post date:** [May 31, 2020, 12:33pm UTC](https://discourse.julialang.org/t/10x-slowdown-when-passing-function-as-argument/40382/14 "2020-05-31T12:33:38Z")

</div>

Am I missing something or does this not make any sense at all?

---

<div class="post-metadata">

**Author:** ![jlchan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jlchan/32/10958_2.png) [@jlchan](https://discourse.julialang.org/u/jlchan)\
**Post date:** [May 31, 2020, 12:42pm UTC](https://discourse.julialang.org/t/10x-slowdown-when-passing-function-as-argument/40382/15 "2020-05-31T12:42:32Z")

</div>

Yeah, I’m having trouble seeing a pattern or rule.

---

<div class="post-metadata">

**Author:** ![Elrod](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/elrod/32/22461_2.png) [@Elrod](https://discourse.julialang.org/u/Elrod)\
**Post date:** [May 31, 2020, 1:03pm UTC](https://discourse.julialang.org/t/10x-slowdown-when-passing-function-as-argument/40382/16 "2020-05-31T13:03:52Z")

</div>

Functions passed functions as arguments will automatically specialize if they call said function.  
That is

```julia
inner(f,x) = f(x) # sort function args first to support `do` syntax

```

should specialize because it is calling `f`, but

```julia
pass(f,x) = inner(f,x)

```

won’t necessarilly, because it isn’t calling `f`.

Let’s use `@noinline` to try and make this more clear.

```julia
function outer000(f) # 0 v 1 bools on specialization for outer, pass, inner
    @noinline function inner(f,x)
        return f(2*x)
    end
    @noinline function pass(f,x)
        return inner(f,x)
    end
    out = zeros(1000)
    for i = 1:1000
        out[i] = pass(f,i)
    end
    out 
end
function outer001(f) # 0 v 1 bools on specialization for outer, pass, inner
    @noinline function inner(f::Fi,x) where {Fi}
        return f(2*x)
    end
    @noinline function pass(f,x)
        return inner(f,x)
    end
    out = zeros(1000)
    for i = 1:1000
        out[i] = pass(f,i)
    end
    out 
end
function outer010(f) # 0 v 1 bools on specialization for outer, pass, inner
    @noinline function inner(f,x)
        return f(2*x)
    end
    @noinline function pass(f::Fp,x) where {Fp}
        return inner(f,x)
    end
    out = zeros(1000)
    for i = 1:1000
        out[i] = pass(f,i)
    end
    out 
end
function outer011(f) # 0 v 1 bools on specialization for outer, pass, inner
    @noinline function inner(f::Fi,x) where {Fi}
        return f(2*x)
    end
    @noinline function pass(f::Fp,x) where {Fp}
        return inner(f,x)
    end
    out = zeros(1000)
    for i = 1:1000
        out[i] = pass(f,i)
    end
    out 
end
function outer100(f::Fo) where {Fo} # 0 v 1 bools on specialization for outer, pass, inner
    @noinline function inner(f,x)
        return f(2*x)
    end
    @noinline function pass(f,x)
        return inner(f,x)
    end
    out = zeros(1000)
    for i = 1:1000
        out[i] = pass(f,i)
    end
    out 
end
function outer101(f::Fo) where {Fo} # 0 v 1 bools on specialization for outer, pass, inner
    @noinline function inner(f::Fi,x) where {Fi}
        return f(2*x)
    end
    @noinline function pass(f,x)
        return inner(f,x)
    end
    out = zeros(1000)
    for i = 1:1000
        out[i] = pass(f,i)
    end
    out 
end
function outer110(f::Fo) where {Fo} # 0 v 1 bools on specialization for outer, pass, inner
    @noinline function inner(f,x)
        return f(2*x)
    end
    @noinline function pass(f::Fp,x) where {Fp}
        return inner(f,x)
    end
    out = zeros(1000)
    for i = 1:1000
        out[i] = pass(f,i)
    end
    out 
end
function outer111(f::Fo) where {Fo} # 0 v 1 bools on specialization for outer, pass, inner
    @noinline function inner(f::Fi,x) where {Fi}
        return f(2*x)
    end
    @noinline function pass(f::Fp,x) where {Fp}
        return inner(f,x)
    end
    out = zeros(1000)
    for i = 1:1000
        out[i] = pass(f,i)
    end
    out 
end

```

This yields:

```julia
f(x) = exp(x)
julia> @btime outer000(f);
  46.415 μs (1979 allocations: 38.84 KiB)

julia> @btime outer001(f);
  47.216 μs (1979 allocations: 38.84 KiB)

julia> @btime outer010(f);
  46.122 μs (1979 allocations: 38.84 KiB)

julia> @btime outer011(f);
  45.612 μs (1979 allocations: 38.84 KiB)

julia> @btime outer100(f);
  37.679 μs (1979 allocations: 38.84 KiB)

julia> @btime outer101(f);
  38.109 μs (1979 allocations: 38.84 KiB)

julia> @btime outer110(f);
  12.724 μs (1 allocation: 7.94 KiB)

julia> @btime outer111(f);
  12.402 μs (1 allocation: 7.94 KiB)

```

It’s the two functions not calling `f` that matter – they must either inline or specialize.

---

<div class="post-metadata">

**Author:** ![jlchan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/jlchan/32/10958_2.png) [@jlchan](https://discourse.julialang.org/u/jlchan)\
**Post date:** [June 4, 2020, 6:04pm UTC](https://discourse.julialang.org/t/10x-slowdown-when-passing-function-as-argument/40382/17 "2020-06-04T18:04:41Z")

</div>

Seems like Julia’s function passing behavior is tricky to catch and profile - the specialization solution is the same as [this solution proposed in 2018](https://discourse.julialang.org/t/splatting-arguments-causes-30x-slow-down/16964), and folks in the thread noted that diagnosing this with `@code_warntype` can be tricky.

If these functions behave this way, what about for functions inherited by closure? I realized a lot of my code optimizations over the last week had to do with calling closure-inherited functions in a specific way to ensure specialization.
