Anonymous function performance

Hi all,
Following code shows performance penalty associate with anonymous function. However the @code_llvm shows both code is the same. Am I benchmark it in a wrong way?


using BenchmarkTools

f1(x)=5+6x

f2(x)=7+8x

f3(x)=9+10x

function f_all(x)
return f1(x)+f2(x)+f3(x)
end

let
macro_f=eval(Meta.parse(“x->f1(x)+f2(x)+f3(x)”))
@btime f_all(rand(1:100))
@btime macro_f(rand(1:100))
end

Hi,

The problem appears to be that when you assign the anonymous function to a global variable and you have the penalties that apply to (non-const) globals. Named functions are const by-default.

Try

const macro_f=eval(Meta.parse("x->f1(x)+f2(x)+f3(x)"))

instead and you should see the time difference disappear. (BTW: quoting your code in triple backticks ```code``` makes it easier to read and paste into a Julia shell - the quotation marks in your code got mangled by whatever you used to paste in from.)

Hope that helps

3 Likes

Another sidenote: I don’t know how you are constructing your anonymous functions but you’re usually better off constructing them from symbols rather than strings, e.g.,

const macro_f2 = eval(:(x -> f1(x) + f2(x) + f3(x)))
1 Like

But why construct the anonymous function with a macro at all? Why not just:

mf2 = x -> f1(x) + f2(x) + f3(x)

?

This is a little bizarre actually. I initially thought of the common trap where @btime ... evaluates in the global scope, so it would use global variables if you didn’t $-interpolate values into the expression prior to evaluation. But macro_f is assigned in a local let block, so this fails to find a global macro_f and errors. I’m assuming OP previously defined a non-const global macro_f in the session, which has runtime costs as explained already. Even if there wasn’t a global macro_f, you could interpolate the local macro_f's value for benchmarking @btime $macro_f(rand(1:100)), which has the same performance as f_all.

2 Likes

Yes, I removed the let block to get it to work

The final function consist of many terms based on user configuration. I don’t know if there is other way to construct the function at run time. I have tried sum the result from vector of function but it is not very performant.

I’m trying to implement a time series model, which consist of configurable terms e.g. trend, seasonality, auto-regression etc. These terms is optional and operations among terms can be addition or multiplication.
Is there any other performant way to achieve this?

I tend to do quite a bit of this - the most performant way is usually to build up the syntax tree directly, for example

expr = :(f1(x))
if some_option_is_true
    expr = :($expr + f2(x))
end
final_expr = :(x -> $expr)
func = eval(final_expr)

Alternatively, use something like ModelingToolkit.jl or Symbolics.jl directly. (They largely do the same thing under the hood with a few other bells-and-whistles.)

1 Like

It’s generally better to construct the function definition and then eval the whole thing so that you get a constant binding automatically. If you want to store the function in a data structure this approach is fine though. You’ll get a dispatch hit at the first point you pass it as an argument but the function that actually calls the code will get specialized on it so that will be fast after the initial invocation.

3 Likes