# Why is closure slower than handmade callable struct?

**URL:** https://discourse.julialang.org/t/why-is-closure-slower-than-handmade-callable-struct/80361
**Category:** Performance
**Tags:** struct, closure
**Created:** [May 2, 2022, 10:35am UTC](https://discourse.julialang.org/t/why-is-closure-slower-than-handmade-callable-struct/80361 "2022-05-02T10:35:58Z")
**Posts on this page:** 5
**Page:** 1

<div class="post-metadata">

### Author: ![FedericoStra](https://avatars.discourse-cdn.com/v4/letter/f/76d3ee/32.png) [@FedericoStra](https://discourse.julialang.org/u/FedericoStra)
#### Post date: [May 2, 2022, 10:35am UTC](https://discourse.julialang.org/t/why-is-closure-slower-than-handmade-callable-struct/80361/1 "2022-05-02T10:35:58Z")

</div>

I’m having a hard time trying to understand why in the following code a closure is slower than an equivalent hand-made callable `struct`.

```julia
using BenchmarkTools
using Interpolations
using LinearAlgebra: norm
using StaticArrays
using QuadGK

speed(spline, t) = norm(Interpolations.gradient1(spline, t))

# closure version

length_closure(spline) = quadgk(t -> speed(spline, t), 0, length(spline))

# hand-made struct version

struct LenIntegrand{S}
    spline::S
end

(li::LenIntegrand)(t) = speed(li.spline, t)

length_struct(spline) = quadgk(LenIntegrand(spline), 0, length(spline))

# benchmarking code

θs = range(0, 2π, length=25)[1:end-1]
xs, ys = 2cos.(θs), 0.5sin.(θs)
vec = [SA[x,y] for (x,y) in zip(xs, ys)]
spl = extrapolate(interpolate(vec, BSpline(Cubic(Periodic(OnCell())))), Periodic())

@benchmark length_closure($spl) # ~ 65 μs
@benchmark length_struct($spl) # ~ 45 μs

```

I can observe the same behavior in other similar examples, where a hand-made `struct` always beats a closure for the purpose of fixing some arguments, whereas I expected the two implementation to be pretty much equivalent. `@code_warntype` does not seem to help me understand the underlying issue here.

---

<div class="post-metadata">

### Author: ![kristoffer.carlsson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kristoffer.carlsson/32/22_2.png) [@kristoffer.carlsson](https://discourse.julialang.org/u/kristoffer.carlsson)
#### Post date: [May 2, 2022, 10:51am UTC](https://discourse.julialang.org/t/why-is-closure-slower-than-handmade-callable-struct/80361/2 "2022-05-02T10:51:01Z")

</div>

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

Changing some instances of

> <https://github.com/JuliaMath/QuadGK.jl/blob/298f76e71be8a36d6e3f16715f601c3d22c2241c/src/adapt.jl#L180>

to `quadgk(f::F, segs...; kws...) where {F}` would confirm that.

---

<div class="post-metadata">

### Author: ![FedericoStra](https://avatars.discourse-cdn.com/v4/letter/f/76d3ee/32.png) [@FedericoStra](https://discourse.julialang.org/u/FedericoStra)
#### Post date: [May 2, 2022, 11:09am UTC](https://discourse.julialang.org/t/why-is-closure-slower-than-handmade-callable-struct/80361/3 "2022-05-02T11:09:40Z")

</div>

Ah great catch! I always try to remind of missed `::Function` specialization in my code, but it didn’t occur to me in this case.

---

<div class="post-metadata">

### Author: ![FedericoStra](https://avatars.discourse-cdn.com/v4/letter/f/76d3ee/32.png) [@FedericoStra](https://discourse.julialang.org/u/FedericoStra)
#### Post date: [May 2, 2022, 11:33am UTC](https://discourse.julialang.org/t/why-is-closure-slower-than-handmade-callable-struct/80361/4 "2022-05-02T11:33:51Z")

</div>

I’m left with a doubt though. After running both versions, `methods(quadgk)[1].specializations` shows that `quadgk` got specialized three times:

- for `::LenIntegrand{Interpolations.Extrapolation{SVector{2, Float64}, 1, Interpolations.BSplineInterpolation{SVector{2, Float64}, 1, Vector{SVector{2, Float64}}, BSpline{Cubic{Periodic{OnCell}}}, Tuple{Base.OneTo{Int64}}}, BSpline{Cubic{Periodic{OnCell}}}, Periodic{Nothing}}}` (the hand-made callable struct)
- for `::Function`
- for `::var"#1#2"{Interpolations.Extrapolation{SVector{2, Float64}, 1, Interpolations.BSplineInterpolation{SVector{2, Float64}, 1, Vector{SVector{2, Float64}}, BSpline{Cubic{Periodic{OnCell}}}, Tuple{Base.OneTo{Int64}}}, BSpline{Cubic{Periodic{OnCell}}}, Periodic{Nothing}}}` (the closure).

Why did `quadgk` get specialized at all, in apparent contradiction to its signature and [https://docs.julialang.org/en/v1/manual/performance-tips/#Be-aware-of-when-Julia-avoids-specializing](https://docs.julialang.org/en/v1/manual/performance-tips/#Be-aware-of-when-Julia-avoids-specializing)?  
Given that `quadgk` got specialized anyway, why is there still a performance difference between the two versions?

---

<div class="post-metadata">

### Author: ![kristoffer.carlsson](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kristoffer.carlsson/32/22_2.png) [@kristoffer.carlsson](https://discourse.julialang.org/u/kristoffer.carlsson)
#### Post date: [May 2, 2022, 1:49pm UTC](https://discourse.julialang.org/t/why-is-closure-slower-than-handmade-callable-struct/80361/5 "2022-05-02T13:49:38Z")

</div>

Actually, I think this has already been fixed on the master branch of QuadGK. Probably by [https://github.com/JuliaMath/QuadGK.jl/commit/298f76e71be8a36d6e3f16715f601c3d22c2241c](https://github.com/JuliaMath/QuadGK.jl/commit/298f76e71be8a36d6e3f16715f601c3d22c2241c) which added some `where F`.

Using the master branch I get the same performance with both an anonymous function and a callable struct.
