# Why is functor slower than eval'ed function?

**URL:** https://discourse.julialang.org/t/why-is-functor-slower-than-evaled-function/43840
**Category:** Performance
**Tags:** performance, functors
**Created:** [July 28, 2020, 9:20pm UTC](https://discourse.julialang.org/t/why-is-functor-slower-than-evaled-function/43840 "2020-07-28T21:20:14Z")
**Posts on this page:** 9
**Page:** 1

<div class="post-metadata">

### Author: ![kevbonham](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kevbonham/32/216165_2.png) [@kevbonham](https://discourse.julialang.org/u/kevbonham)
#### Post date: [July 28, 2020, 9:20pm UTC](https://discourse.julialang.org/t/why-is-functor-slower-than-evaled-function/43840/1 "2020-07-28T21:20:14Z")

</div>

I have a situation where I’d like to build some functions based on different inputs - using a callable function seems really clear to me, and has the functionality I want. I’d rather not use `eval` and other metaprogramming stuff because it seems way more complicated, but it seems like functors have a huge performance penalty.

It’s also possible I’m missing something critical and obvious, but this is my first foray into doing anything like this. Here’s a super simplified example:

```julia
julia> function makef(p1, p2, p3)
           eval(Meta.parse("f(x) = $p1*x^2 + $p2*x + $p3"))
       end
makef (generic function with 1 method)

julia> makef(3, 2, 1)
f (generic function with 1 method)

julia> struct Polynomial
           p1
           p2
           p3
       end

julia> function (p::Polynomial)(x)
           p.p1*x^2 + p.p2*x + p.p3
       end

julia> g = Polynomial(3,2,1)
Polynomial(3, 2, 1)

julia> using BenchmarkTools
[Info: Precompiling BenchmarkTools [6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf]

julia> @btime f(2)
  0.030 ns (0 allocations: 0 bytes)
17

julia> @btime g(2)
  55.050 ns (0 allocations: 0 bytes)
17

```

Based on other discussions around stuff like this, I’m guessing something is being compiled away in `f` but not in `g`, but I’m not clear on why that would be, or if there’s a way to help `g` along a bit.

---

<div class="post-metadata">

### Author: ![tomerarnon](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tomerarnon/32/3170_2.png) [@tomerarnon](https://discourse.julialang.org/u/tomerarnon)
#### Post date: [July 28, 2020, 9:41pm UTC](https://discourse.julialang.org/t/why-is-functor-slower-than-evaled-function/43840/2 "2020-07-28T21:41:23Z")

</div>

I think it’s the BenchmarkTools `$` global variables thing.

```julia
julia> @btime g(2)
  85.875 ns (0 allocations: 0 bytes)
17

julia> @btime $g(2)
  0.027 ns (0 allocations: 0 bytes)
17

```

`f` doesn’t have the same problem:

```julia
julia> @btime f(2)
  0.027 ns (0 allocations: 0 bytes)
11

julia> @btime $f(2)
  0.028 ns (0 allocations: 0 bytes)
11

```

---

<div class="post-metadata">

### Author: ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)
#### Post date: [July 28, 2020, 9:44pm UTC](https://discourse.julialang.org/t/why-is-functor-slower-than-evaled-function/43840/3 "2020-07-28T21:44:29Z")

</div>

> [@tomerarnon](#):
>
> `f` doesn’t have the same problem:

That’s because functions in Julia are `const`. In contrast, your variable `g` is not a constant.

---

<div class="post-metadata">

### Author: ![bcmichael](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/bcmichael/32/10003_2.png) [@bcmichael](https://discourse.julialang.org/u/bcmichael)
#### Post date: [July 28, 2020, 9:53pm UTC](https://discourse.julialang.org/t/why-is-functor-slower-than-evaled-function/43840/4 "2020-07-28T21:53:01Z")

</div>

The 0.03 ns timing should be a big red flag that the function is being optimized away entirely. I’m pretty sure that’s substantially faster than the clock cycle of any computer I’m familiar with.

---

<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: [July 28, 2020, 9:54pm UTC](https://discourse.julialang.org/t/why-is-functor-slower-than-evaled-function/43840/5 "2020-07-28T21:54:14Z")

</div>

> [@kevbonham](#):
>
> ```julia
> struct Polynomial
> p1
> p2
> p3
> end
> 
> ```

This code has a very important problem. The parameters of your struct aren’t typed. This means that the compiler will often have to do dispatch to figure out how to multiply `p1*x`. The solution is to make `Polynomial` a parimetric type. IE

```julia
struct Polynomial{T}
    p1::T
    p2::T
    p3::T
 end

```

---

<div class="post-metadata">

### Author: ![tomerarnon](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tomerarnon/32/3170_2.png) [@tomerarnon](https://discourse.julialang.org/u/tomerarnon)
#### Post date: [July 29, 2020, 12:44am UTC](https://discourse.julialang.org/t/why-is-functor-slower-than-evaled-function/43840/6 "2020-07-29T00:44:34Z")

</div>

Yes, all of the above seems to make a difference, and my benchmarks were indeed wrong.  
These are the corrected benchmarks:

```julia-repl
julia> makef(3, 2, 1)
f (generic function with 1 method)

julia> g = Polynomial(3, 2, 1)
Polynomial{Int64}(3, 2, 1)

julia> h = Poly(3, 2, 1)
Poly(3, 2, 1)

julia> @btime f(Ref(2)[]);
  1.644 ns (0 allocations: 0 bytes)

julia> @btime g(Ref(2)[]);
  22.700 ns (0 allocations: 0 bytes)

julia> @btime $g(Ref(2)[]);
  1.643 ns (0 allocations: 0 bytes)

julia> @btime h(Ref(2)[]);
  79.174 ns (0 allocations: 0 bytes)

julia> @btime $h(Ref(2)[]);
  61.887 ns (0 allocations: 0 bytes)

```

---

<div class="post-metadata">

### Author: ![kevbonham](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kevbonham/32/216165_2.png) [@kevbonham](https://discourse.julialang.org/u/kevbonham)
#### Post date: [July 29, 2020, 2:38pm UTC](https://discourse.julialang.org/t/why-is-functor-slower-than-evaled-function/43840/7 "2020-07-29T14:38:14Z")

</div>

Thanks everyone! This is very clear and helpful info. I had one other question that I thought I would include here for the sake of completeness. For reference, on my system, the `eval`ed version is 0.03 ns, and the naive, non-constant functor was 55 ns. Confirmed that if I do `Polynomial{T}` version suggested by @Oscar_Smith, and make it `const` as suggested by @tomerarnon and @stevengj, I get speeds similar to `f(x)`.

I wasn’t sure if restricting the types of the fields without parameterizing the whole type would work, so I tried

```julia
julia> struct PolyReal
           p1::Real
           p2::Real
           p3::Real
       end

```

and it appears to be just as fast as `f(x)`.

> **benchmark**
>
> ```julia
> julia> function (p::PolyReal)(x)
> p.p1*x^2 + p.p2*x + p.p3
> end
> 
> julia> const polyreal = PolyReal(3,2,1)
> PolyReal(3, 2, 1)
> 
> julia> @btime polyreal(2)
> 0.030 ns (0 allocations: 0 bytes)
> 17
> 
> julia> const polyrealmixed = PolyReal(3.,2,1) # in case mixed types cause problems
> PolyReal(3.0, 2, 1)
> 
> julia> @btime polyrealmixed(2) 
> 0.032 ns (0 allocations: 0 bytes)
> 17.0
> 
> ```

At least in this case. So it appears that in at least some cases, the compiler can peer into the struct to get the types. But this brings up another question - is there a good heuristic for when using `{T}` would be better, vs restricting types of individual fields (assuming I can do the latter in all the situations I care about)?

Or more specifically, aside from keeping all parameters the same type (which I understand would have benefits in some cases), is there a difference from the compiler’s perspective between:

```julia
struct Foo{T} where T <:Real
    x::T
    y::T
end

# and

struct Bar
    x::Real
    y::Real
end

```

??

---

<div class="post-metadata">

### Author: ![tomerarnon](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tomerarnon/32/3170_2.png) [@tomerarnon](https://discourse.julialang.org/u/tomerarnon)
#### Post date: [July 29, 2020, 2:40pm UTC](https://discourse.julialang.org/t/why-is-functor-slower-than-evaled-function/43840/8 "2020-07-29T14:40:18Z")

</div>

Note that in my corrected benchmarks, I use `Ref(2)[]` as the argument to the function. If you pass in a literal `2`, the compiler optimizes the function away, since it is being evaluated for a compile time constant. Try using the “Ref trick” to be sure you’re getting accurate results.

To answer your main question though:

> So it appears that in at least some cases, the compiler can peer into the struct to get the types.

Yes, whenever types are given, the compiler has access to them. In your example for `Foo`, a `Foo{Int}` object declares to the compiler that `x` and `y` are `Ints`. The restriction on `T` tells the type `Foo` that it may only store things that are subtypes of `Real`. In your `Bar` example, the object tells the compiler that `x` and `y` are subsets of `Real`, but it doesn’t say which concrete type they are (it has no way of knowing). So they can be `Int8s`, `Float64s`, etc., meaning many optimizations are impossible with these values. Moreover, they may be different types in `Bar`, whereas they cannot be in `Foo{T}`.

> But this brings up another question - is there a good heuristic for when using `{T}` would be better, vs restricting types of individual fields (assuming I can do the latter in all the situations I care about)?

Concrete types are critical for performance. If performance doesn’t matter in some part of code, then restricting to abstract types (like `Bar`) or not restricting at all is fine. If the type must be flexible in what it can store (Ints, Floats, etc) but still needs to be performant, that’s when parameters like `T` are needed.

---

<div class="post-metadata">

### Author: ![kevbonham](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kevbonham/32/216165_2.png) [@kevbonham](https://discourse.julialang.org/u/kevbonham)
#### Post date: [July 29, 2020, 2:48pm UTC](https://discourse.julialang.org/t/why-is-functor-slower-than-evaled-function/43840/9 "2020-07-29T14:48:25Z")

</div>

I was wondering about that! Should have asked what that was about. Interesting…

```julia
julia> @btime polyrealmixed(Ref(2)[])
  52.802 ns (2 allocations: 32 bytes)
17.0

julia> @btime polyreal(Ref(2)[])
  46.814 ns (0 allocations: 0 bytes)
17

julia> @btime polytc(Ref(2)[]) # typed constant
  1.371 ns (0 allocations: 0 bytes)
17

```

So I guess this answers my earlier question pretty definitively 🙂
