# Inside closures, captured constructors are much slower than captured functions

**URL:** https://discourse.julialang.org/t/inside-closures-captured-constructors-are-much-slower-than-captured-functions/99638
**Category:** Performance
**Tags:** constructors, closure
**Created:** [May 31, 2023, 12:06am UTC](https://discourse.julialang.org/t/inside-closures-captured-constructors-are-much-slower-than-captured-functions/99638 "2023-05-31T00:06:24Z")
**Posts on this page:** 3
**Page:** 1

<div class="post-metadata">

### Author: ![matthias314](https://avatars.discourse-cdn.com/v4/letter/m/a88e4f/32.png) [@matthias314](https://discourse.julialang.org/u/matthias314)
#### Post date: [May 31, 2023, 12:06am UTC](https://discourse.julialang.org/t/inside-closures-captured-constructors-are-much-slower-than-captured-functions/99638/1 "2023-05-31T00:06:24Z")

</div>

I have noticed that inside function closures, using a captured variable with a type value as a constructor is much slower than using a variable with a function value. Consider the following (silly) example:

```julia
function g(f, n)
    s = zero(f(n))
    foreach(1:n) do i
        s += f(i)
    end
    s
end

```

Then

```julia
julia> @btime g(Int, 4);
  408.735 ns (1 allocation: 16 bytes)

julia> @btime g(x -> Int(x), 4);
  68.649 ns (1 allocation: 16 bytes)

```

This surprised me because constructors usually behave like functions.  
Is this considered a bug, or is it just the way it is? In the latter case, would it make sense to mention it in the documentation section on the performance of captured variables?

---

<div class="post-metadata">

### Author: ![greatpet](https://avatars.discourse-cdn.com/v4/letter/g/e495f1/32.png) [@greatpet](https://discourse.julialang.org/u/greatpet)
#### Post date: [May 31, 2023, 12:40am UTC](https://discourse.julialang.org/t/inside-closures-captured-constructors-are-much-slower-than-captured-functions/99638/2 "2023-05-31T00:40:52Z")

</div>

Here’s the output of `JET.@report_opt`. The slower version has a more serious type instability. It seems that the lambda function `x -> Int(x)` is treated as if it has a more concrete type than `Int`.

```julia
julia> using JET

julia> include("testclosure.jl"); # defines `g(f, n)` as in the above post

julia> @report_opt g(Int, 4)
═════ 3 possible errors found ═════
┌ @ testclosure.jl:3 foreach(#3, 1 : n)
│┌ @ abstractarray.jl:3073 f(x)
││┌ @ testclosure.jl:4 %7(i)
│││ runtime dispatch detected: %7::DataType(i::Int64)::Any
││└──────────────────────────────────────────
││┌ @ testclosure.jl:4 %6 + %8
│││ runtime dispatch detected: (%6::Any + %8::Any)::Any
││└──────────────────────────────────────────
┌ @ testclosure.jl:2 s = Core.Box()
│ captured variable `s` detected
└──────────────────────────────────────────

julia> @report_opt g(x -> Int(x), 4)
═════ 2 possible errors found ═════
┌ @ testclosure.jl:3 foreach(#3, 1 : n)
│┌ @ abstractarray.jl:3073 f(x)
││┌ @ testclosure.jl:4 %6 + i
│││ runtime dispatch detected: (%6::Any + i::Int64)::Any
││└──────────────────────────────────────────
┌ @ testclosure.jl:2 s = Core.Box()
│ captured variable `s` detected
└──────────────────────────────────────────

```

---

<div class="post-metadata">

### Author: ![tim.holy](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tim.holy/32/52_2.png) [@tim.holy](https://discourse.julialang.org/u/tim.holy)
#### Post date: [May 31, 2023, 12:47pm UTC](https://discourse.julialang.org/t/inside-closures-captured-constructors-are-much-slower-than-captured-functions/99638/3 "2023-05-31T12:47:54Z")

</div>

Anytime you see something like this, get used to doing this:

```julia
julia> using Cthulhu

julia> @descend g(Int, 4)

```

As pointed out by @greatpet, you’ll quickly see that your usage for `foreach` results in a `Core.Box` due to [this issue](https://docs.julialang.org/en/v1/manual/performance-tips/#man-performance-captured).

EDIT: maybe this is what you meant by

> would it make sense to mention it in the documentation section on the performance of captured variables?

I don’t think it’s that much of a special case, but if you think you can come up with something that adds to that discussion, try submitting a PR?

Once you fix that, you may (?) also need to force-specialize on `f`, i.e., `g(f::F, n) where F` to circumvent Julia’s heuristics for not specializing on types-as-arguments.
