Using SymPy in Julia to convert a string of an expression I noticed a performance difference of factor ~3500 between the implementation of a native Julia function fast_fct
and the SymPy function slow_fct
generated from a string. Is there a way to speed up the SymPy function or is there a different, faster way achieving the same?
Please confer How to lambdify a list of strings with SymPy in Julia? for the function string_to_function
.
Minimal Working Example:
using SymPy
function string_to_function(fct_string)
expression = SymPy.sympify.(fct_string)
variables = free_symbols(expression)
function(args...)
subs.(expression, (variables .=> args)...)
end
end
function fast_fct(x, y, z)
return x y z
end
slow_fct = string_to_function("x y z")
Benchmarking
N = 100000
@time for i in 0:N
x, y, z = rand(3)
fast_fct(x, y, z)
end
@time for i in 0:N
x, y, z = rand(3)
slow_fct(x, y, z)
end
with the approximate results
>>> 0.014453 seconds (398.98 k allocations: 16.769 MiB, 40.48% gc time)
>>> 31.364378 seconds (13.04 M allocations: 377.752 MiB, 0.64% gc time, 0.41% compilation time)
CodePudding user response:
Actually with the proper benchmarking the difference is even higher, as there you are measuring also other things...
using BenchmarkTools
x, y, z = rand(3)
@btime fast_fct($x, $y, $z) # 4.500 ns (0 allocations: 0 bytes)
@btime slow_fct($x, $y, $z) # 162.210 μs (119 allocations: 3.22 KiB)
A few observations:
- I don't think this micro-benchmark is much useful, unless you are interested really in these very basic elementary operations. Of course the direct Julia way is almost instantaneous, the sympy way needs to go trough lot of fixed computational costs.
- If performances are important, check out
Symbolics.jl
the native implementation for symbolic computations in Julia. This should be much faster (but still, for an example like this one, it will never go close...). It is however pretty new, and the doc is not yet so good as for sympy.
CodePudding user response:
For this, there are some examples in lambdify
. Antonello points out that Symbolics is likely faster -- they have a much better version of lambdify
-- but here using @eval
is likely good enough:
julia> @btime fast_fct(x...) setup=(x=rand(3))
86.283 ns (4 allocations: 64 bytes)
2.2829680705749293
julia> med_fct = lambdify(SymPy.sympify("x y z"))
#101 (generic function with 1 method)
julia> @btime med_fct(x...) setup=(x=rand(3))
939.393 ns (16 allocations: 304 bytes)
1.5532948656814223
julia> ex = lambdify(SymPy.sympify("x y z"), invoke_latest=false)
:(function var"##321"(x, y, z)
x y z
end)
julia> @eval asfast_fct(x,y,z) = ($ex)(x,y,z) # avoid invoke_latest call
asfast_fct (generic function with 1 method)
julia> @btime asfast_fct(x...) setup=(x=rand(3))
89.872 ns (4 allocations: 64 bytes)
1.1222502647060117