在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中符号计算的本机实现。这应该快得多(但是,对于像这样的例子,它永远不会接近…)。然而,它是相当新的,并且文档还没有那么好,因为症状。