# Surprising runtime behaviour when wrapping functions

**URL:** <https://discourse.julialang.org/t/surprising-runtime-behaviour-when-wrapping-functions/68140>\
**Category:** Performance\
**Tags:** question\
**Created:** [September 14, 2021, 10:10am UTC](https://discourse.julialang.org/t/surprising-runtime-behaviour-when-wrapping-functions/68140 "2021-09-14T10:10:48Z")\
**Posts on this page:** 5\
**Page:** 1

<div class="post-metadata">

**Author:** ![goerch](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/goerch/32/29122_2.png) [@goerch](https://discourse.julialang.org/u/goerch)\
**Post date:** [September 14, 2021, 10:10am UTC](https://discourse.julialang.org/t/surprising-runtime-behaviour-when-wrapping-functions/68140/1 "2021-09-14T10:10:48Z")

</div>

Running the following test program

```julia
using BenchmarkTools

f(x) = x

struct Wrapper
    f::Function
end

function evaluate(f, x)
    return f(x)
end
function evaluate(wrapper::Wrapper, x)
    return wrapper.f(x)
end

function test(f, n)
    [evaluate(f, x) for x in 1:n]
end

w = Wrapper(f)
global n
for n in [10, 100, 1000, 10000, 100000]
    test(f, n)
    @time test(f, n)

    test(w, n)
    @time test(w, n)
end
println()

@btime test(f, 10)
@btime test(w, 10)
@btime test(f, 100)
@btime test(w, 100)
@btime test(f, 1000)
@btime test(w, 1000)
@btime test(f, 10000)
@btime test(w, 10000)
@btime test(f, 100000)
@btime test(w, 100000)
println()

```

with Julia 1.6.1 prints

```julia
  0.000000 seconds (1 allocation: 160 bytes)
  0.000012 seconds (3 allocations: 224 bytes)
  0.000000 seconds (1 allocation: 896 bytes)
  0.000002 seconds (3 allocations: 960 bytes)
  0.000001 seconds (1 allocation: 7.938 KiB)
  0.000022 seconds (493 allocations: 15.656 KiB)
  0.000016 seconds (2 allocations: 78.203 KiB)
  0.000258 seconds (9.49 k allocations: 226.547 KiB)
  0.000218 seconds (2 allocations: 781.328 KiB)
  0.002767 seconds (99.49 k allocations: 2.281 MiB)

  27.968 ns (1 allocation: 160 bytes)
  444.949 ns (3 allocations: 224 bytes)
  56.809 ns (1 allocation: 896 bytes)
  2.180 μs (3 allocations: 960 bytes)
  482.105 ns (1 allocation: 7.94 KiB)
  21.300 μs (492 allocations: 15.64 KiB)
  5.140 μs (2 allocations: 78.20 KiB)
  208.100 μs (9493 allocations: 226.53 KiB)
  43.600 μs (2 allocations: 781.33 KiB)
  2.079 ms (99493 allocations: 2.28 MiB)

```

Is this expected behaviour? Is this a mistake on my side? Can we improve this somehow?

---

<div class="post-metadata">

**Author:** ![mike](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mike/32/39_2.png) [@mike](https://discourse.julialang.org/u/mike)\
**Post date:** [September 14, 2021, 10:17am UTC](https://discourse.julialang.org/t/surprising-runtime-behaviour-when-wrapping-functions/68140/2 "2021-09-14T10:17:03Z")

</div>

> [@goerch](#):
>
> Is this expected behaviour? Is this a mistake on my side? Can we improve this somehow?

Yes, it’s expected. The `f::Function` is an abstract type. You’ll end up with dynamic dispatch when calling `wrapper.f(x)` as shown below:

```julia
julia> @code_warntype evaluate(f, 10)
Variables
  #self#::Core.Const(evaluate)
  f::Core.Const(f)
  x::Int64

Body::Int64
1 ─ %1 = (f)(x)::Int64
└── return %1

julia> @code_warntype evaluate(Wrapper(f), 10)
Variables
  #self#::Core.Const(evaluate)
  wrapper::Wrapper
  x::Int64

Body::Any
1 ─ %1 = Base.getproperty(wrapper, :f)::Function
│ %2 = (%1)(x)::Any
└── return %2

```

Note the `::Any` annotation is the second result.

You could define your `Wrapper` type as

```julia
struct Wrapper{F<:Function}
    f::F
end

```

to ensure it’s a concrete type rather than abstract.

---

<div class="post-metadata">

**Author:** ![goerch](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/goerch/32/29122_2.png) [@goerch](https://discourse.julialang.org/u/goerch)\
**Post date:** [September 14, 2021, 10:28am UTC](https://discourse.julialang.org/t/surprising-runtime-behaviour-when-wrapping-functions/68140/3 "2021-09-14T10:28:20Z")

</div>

Thank you very much for your excellent answer. FYI, I was orienting towards **[NLSolversBase.jl](https://github.com/JuliaNLSolvers/NLSolversBase.jl)**

```julia
mutable struct OnceDifferentiable{TF, TDF, TX} <: AbstractObjective
    f # objective
    df # (partial) derivative of objective
    fdf # objective and (partial) derivative of objective
    F::TF # cache for f output
    DF::TDF # cache for df output
    x_f::TX # x used to evaluate f (stored in F)
    x_df::TX # x used to evaluate df (stored in DF)
    f_calls::Vector{Int}
    df_calls::Vector{Int}
end

```

which doesn’t even annotate the functions.

---

<div class="post-metadata">

**Author:** ![mike](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mike/32/39_2.png) [@mike](https://discourse.julialang.org/u/mike)\
**Post date:** [September 14, 2021, 10:37am UTC](https://discourse.julialang.org/t/surprising-runtime-behaviour-when-wrapping-functions/68140/4 "2021-09-14T10:37:38Z")

</div>

> [@goerch](#):
>
> which doesn’t even annotate the functions.

It’s not _always_ the case that restricting the types of fields to something concrete will improve the performance of code, it usually depends on how it’s being used. Over-parameterising types can sometimes lead to strain on the compiler since passing a different `Function` as `.f` might need to recompile a bunch of new code.

It may be that in `OnceDifferentiable` it doesn’t actually matter, or having an untyped field was more useful. Only the authors of that code would be able to provide a definite answer. Usually best to benchmark those kind of decisions if there’s some doubt.

---

<div class="post-metadata">

**Author:** ![goerch](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/goerch/32/29122_2.png) [@goerch](https://discourse.julialang.org/u/goerch)\
**Post date:** [September 14, 2021, 11:21am UTC](https://discourse.julialang.org/t/surprising-runtime-behaviour-when-wrapping-functions/68140/5 "2021-09-14T11:21:14Z")

</div>

Thank you again for your explanations. This forced me to check **[NLSolversBase](https://github.com/JuliaNLSolvers/NLSolversBase.jl)**. My findings are documented at [https://github.com/JuliaNLSolvers/NLSolversBase.jl/issues/142](https://github.com/JuliaNLSolvers/NLSolversBase.jl/issues/142).

One last remark: it seems parametricity already does the job. Performance seems fine for

```julia
struct Wrapper{F}
    f::F
end

```
