改进Julia中由字符串生成的SymPy函数的性能



在Julia中使用SymPy来转换表达式的字符串,我注意到本地Julia函数fast_fct的实现与从字符串生成的SymPy函数slow_fct之间的性能差异约为3500倍。是否有一种方法可以加速SymPy函数,或者是否有一种不同的、更快的方法来实现相同的功能?

请授予如何在Julia中使用SymPy对字符串列表进行lambtify ?string_to_function.

最小工作示例:

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")

基准测试

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

的近似结果

>>>  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)

对于这一点,lambdify中有一些例子。Antonello指出Symbolics可能更快——他们有一个更好的lambdify版本——但这里使用@eval可能已经足够好了:

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

实际上,通过适当的基准测试,差异甚至更大,因为您还测量了其他东西…

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)

几点观察:

  • 我不认为这个微基准是很有用的,除非你真的对这些非常基本的操作感兴趣。当然,直接的Julia方法几乎是即时的,而sympy方法需要经历大量固定的计算成本。
  • 如果性能很重要,请查看Symbolics.jl,这是Julia中符号计算的本机实现。这应该快得多(但是,对于像这样的例子,它永远不会接近…)。然而,它是相当新的,并且文档还没有那么好,因为症状。

最新更新