提高从 Julia 中的字符串生成的 SymPy 函数的性能
Improving the performance of SymPy function generated from string in Julia
在 Julia 中使用 SymPy 转换表达式的字符串我注意到原生 Julia 函数 fast_fct
的实现与从细绳。有没有一种方法可以加快 SymPy 函数的速度,或者是否有一种不同的、更快的方法来实现相同的功能?
请赋予 功能 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)
实际上,通过适当的基准测试,差异甚至更大,因为您还测量了其他东西...
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 中符号计算的本机实现。这应该快得多(但是,对于像这样的例子,它永远不会接近......)。然而它很新,文档还没有 sympy 好。
为此,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
在 Julia 中使用 SymPy 转换表达式的字符串我注意到原生 Julia 函数 fast_fct
的实现与从细绳。有没有一种方法可以加快 SymPy 函数的速度,或者是否有一种不同的、更快的方法来实现相同的功能?
请赋予 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)
实际上,通过适当的基准测试,差异甚至更大,因为您还测量了其他东西...
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 中符号计算的本机实现。这应该快得多(但是,对于像这样的例子,它永远不会接近......)。然而它很新,文档还没有 sympy 好。
为此,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