Performance problem to apply a set of functions to one data


When we have a variable number of functions fs = [f_1, f_2, ..., f_3] and we want to apply these functions to only one value of x. I’m struggling with performance problems. See:

julia> f1(x) = x^2
julia> f2(x) = x^2 + x

julia> calc(x::Float64, fs::Vector{Function}) =
          Float64[ f(x) for f in fs ];

julia> calc(4.0, [f1, f2])
2-element Array{Float64,1}:

The answer is right, but there is a performance issue:

julia> @code_warntype calc(4.0, [f1, f2])
  f::Function **RED: HERE**

4 ┄ %19 = @_6::Tuple{Function,Int64}::Tuple{Function,Int64} **RED: HERE**
│   %22 = (f)(x)::Any                                       **RED: HERE**

I think it’s because Function is an abstract type. So I tried use a Tuple instead of Vector, but the problem persists. The code:

julia> calc(x::Float64, fs::Tuple{Vararg{Function}}) =
                  Float64[ f(x) for f in fs ]

julia> @code_warntype calc(4.0, (f1, f2))
  f::Union{typeof(f1), typeof(f2)} **RED: HERE**
4 ┄ %19 = @_6::Union{Tuple{typeof(f1),Int64}, 
Tuple{typeof(f2),Int64}} **HERE**
│   %22 = (f)(x)::Any **AND HERE**

How can I solve this problem? Thanks.

I think you’re managing to prevent specialisation. calc(x, fs) = [f(x) for f in fs] gives type-stable results with a tuple of functions.

You can define it with calc(x::Number, fs::Tuple) = ... if you’d like to remember your intentions, but there’s no need for more specific annotations.

1 Like

Firstly you don’t need to annotate calc with type. Also you can achieve that without passing in multiple functions. Just use broadcasting with |>.

Any way this will work

f1(x) = x^2
f2(x) = x^2 + x
calc(x, fs) = Ref(x) .|> fs
@code_warntype calc(4.0, (f1, f2))

but I suggest doing it this way

Ref(4.0) .|> (f1, f2)
1 Like

The issue is Float64[] which is interacting weird with the functions fs.

1 Like