Understanding eval


#1

I’m trying to understand why there is such a performance difference calling eval vs. just typing it in. The difference doesn’t depend on whether the expression depends on any variables. How can I make the eval version much faster? I need to use eval because this is genetic programming code and the expressions are generated on the fly.

Thanks!

julia> ex=:(100+cos(4.0)/4.0*sin(1.0)+123.123)
:(100 + (cos(4.0) / 4.0) * sin(1.0) + 123.123)

julia> eval(ex)
222.9854944646596

julia> @btime eval(ex)
  115.154 μs (54 allocations: 3.38 KiB)
222.9854944646596

julia> @btime 100+cos(4.0)/4.0*sin(1.0)+123.123
  17.817 ns (0 allocations: 0 bytes)
222.9854944646596

#2

Seems to be worse in Julia 1.0 than 0.6.


#3

In the second case you are benchmarking the function

f() = 100+cos(4.0)/4.0*sin(1.0)+123.123

Julia will parse the source code, lower it, infer it, optimize it in Julia, send it to LLVM for further optimization, native code will be generated. This happens once which is visible the first time it is called

julia> @time f()
  0.030461 seconds (105.33 k allocations: 5.543 MiB, 28.25% gc time)
222.9854944646596

julia> @time f()
  0.000003 seconds (5 allocations: 176 bytes)
222.9854944646596

With your eval expression you have to start from scratch every time so clearly this will be slower.

For your genetic programming code, what subset of Julia do you need to support? It might be better to write your own little interpreter instead for that subset instead of throwing the whole compiler chain on every single expression. At least if you are only going to evaluate the expression a few times.


#4

Thanks for the response Kristoffer. This is for ExprOptimization.jl which performs genetic programming (and other algorithms) over context-free grammars. The grammar is specified by the user, but is known ahead of time, including all the possible symbols and function calls.


#5

Would julia - O 0 potentially speed this up slightly?


#6

You should create the function with eval (i.e. do f = eval(:(......))) and call that function (you may need invokelatest) if you need to call that function multiple times.


#7

Generating functions helps speed up multiple calls to the same function. However, repeatedly generating many functions causes the performance of all function calls to degrade.

See: https://github.com/JuliaLang/julia/issues/18446

I haven’t tested this lately. But is there any reason to believe that this issue has changed with Julia 1.0?