我写了一个Julia代码,该代码计算高斯函数的积分,并且我有一个内核函数,它被一遍又一遍地调用。
根据Julia内置的Profile
模块,这是我在实际计算中花费的大部分时间,因此我想看看是否有任何方法可以改进它。
这是一个递归函数,我以一种简单的方式实现了它。由于我不太习惯递归函数,所以也许有人对如何改进它有一些想法/建议(从纯理论算法的角度和/或利用JIT编译器的特殊优化方法)。
您在这里:
"""Returns the integral of an Hermite Gaussian divided by the Coulomb operator."""
function Rtuv{T<:Real}(t::Int, u::Int, v::Int, n::Int, p::Real, RPC::Vector{T})
if t == u == v == 0
return (-2.0*p)^n * boys(n,p*norm(RPC)^2)
elseif u == v == 0
if t > 1
return (t-1)*Rtuv(t-2, u, v, n+1, p, RPC) +
RPC[1]*Rtuv(t-1, u, v, n+1, p, RPC)
else
return RPC[1]*Rtuv(t-1, u, v, n+1, p, RPC)
end
elseif v == 0
if u > 1
return (u-1)*Rtuv(t, u-2, v, n+1, p, RPC) +
RPC[2]*Rtuv(t, u-1, v, n+1, p, RPC)
else
return RPC[2]*Rtuv(t, u-1, v, n+1, p ,RPC)
end
else
if v > 1
return (v-1)*Rtuv(t, u, v-2, n+1, p, RPC)
RPC[3]*Rtuv(t, u, v-1, n+1, p, RPC)
else
return RPC[3]*Rtuv(t, u, v-1, n+1, p, RPC)
end
end
end
不要对功能boys
给予太多关注,因为根据探查器,它并不那么繁琐。
只是想了解数字范围:通常,第一个呼叫来自t+u+v
,范围从0
到3
,而n
总是始于0
欢呼
对于较小的
我在这种情况下基准测试很差,没有插值通过的参数。如果做得好,我总是会接受公认的答案中所介绍的方法,所以总是更快!t,u,v
,生成的版本会比较慢,我相信原因是因为编译器未优化表达式。
更笼统地说,编译器是否会识别琐碎的情况,例如乘以零和一并进行优化?
对我自己的答案:用@code_llvm
快速检查简单代码似乎不是这种情况。
答案 0 :(得分:1)
这可能适合您的情况:您可以使用生成的函数“记住”整个编译方法,并在第一次调用后摆脱所有递归。
由于t
,u
和v
将保持较小,因此您可以为递归生成完全扩展的代码。为简单起见,假设伪造的
boys(n::Int, x::Real) = n + x
然后
function Rtuv_expr(t::Int, u::Int, v::Int, n, p, RPC)
ninc = :($n + 1)
if t == u == v == 0
:((-2.0 * $p)^$n * boys($n, $p * norm($RPC)^2))
elseif u == v == 0
if t > 1
:($(t-1) * $(Rtuv_expr(t-2, u, v, ninc, p, RPC)) +
$RPC[1] * $(Rtuv_expr(t-1, u, v, ninc, p, RPC)))
else
:($RPC[1] * $(Rtuv_expr(t-1, u, v, ninc, p, RPC)))
end
elseif v == 0
if u > 1
:($(u-1) * $(Rtuv_expr(t, u-2, v, ninc, p, RPC)) +
$RPC[2] * $(Rtuv_expr(t, u-1, v, ninc, p, RPC)))
else
:($RPC[2] * $(Rtuv_expr(t, u-1, v, ninc, p, RPC)))
end
else
if v > 1
:($(v-1) * $(Rtuv_expr(t, u, v-2, ninc, p, RPC)) +
$RPC[3] * $(Rtuv_expr(t, u, v-1, ninc, p, RPC)))
else
:($RPC[3] * $(Rtuv_expr(t, u, v-1, ninc, p, RPC)))
end
end
end
将为您生成完全展开的表达式,如下所示:
julia> Rtuv_expr(1, 2, 1, 0, 0.1, rand(3))
:(([0.868194, 0.928591, 0.295344])[3] * (1 * (([0.868194, 0.928591, 0.295344])[1] * ((-2.0 * 0.1) ^ (((0 + 1) + 1) + 1) * boys(((0 + 1) + 1) + 1, 0.1 * norm([0.868194, 0.928591, 0.295344]) ^ 2))) + ([0.868194, 0.928591, 0.295344])[2] * (([0.868194, 0.928591, 0.295344])[2] * (([0.868194, 0.928591, 0.295344])[1] * ((-2.0 * 0.1) ^ ((((0 + 1) + 1) + 1) + 1) * boys((((0 + 1) + 1) + 1) + 1, 0.1 * norm([0.868194, 0.928591, 0.295344]) ^ 2))))))
我们可以将它们塞入Rtuv
类型的generated function Val
中。对于T
,U
和V
的每个不同组合,此函数将使用Rtuv_expr
编译各自的表达式,然后使用此方法-不再进行递归:
@generated function Rtuv{T, U, V, X<:Real}(::Type{Val{T}}, ::Type{Val{U}}, ::Type{Val{V}},
n::Int, p::Real, RPC::Vector{X})
Rtuv_expr(T, U, V, :n, :p, :RPC)
end
您必须用t
包裹的u
,v
,Val
来调用它,但是:
julia> Rtuv(Val{1}, Val{2}, Val{1}, 0, 0.1, rand(3))
-0.0007782250832001092
如果您测试这样的小循环,
for t = 0:3, u = 0:3, v = 0:3
println(Rtuv(Val{t}, Val{u}, Val{v}, 0, 0.1, [1.0, 2.0, 3.0]))
end
第一次运行将需要一些时间,但是由于使用的方法已经编译,因此运行很快。