在Julia

时间:2018-07-03 07:09:30

标签: performance recursion julia

我写了一个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,范围从03,而n总是始于0

欢呼

编辑-新信息

对于较小的t,u,v,生成的版本会比较慢,我相信原因是因为编译器未优化表达式。 我在这种情况下基准测试很差,没有插值通过的参数。如果做得好,我总是会接受公认的答案中所介绍的方法,所以总是更快!

更笼统地说,编译器是否会识别琐碎的情况,例如乘以零和一并进行优化?

对我自己的答案:用@code_llvm快速检查简单代码似乎不是这种情况。

1 个答案:

答案 0 :(得分:1)

这可能适合您的情况:您可以使用生成的函数“记住”整个编译方法,并在第一次调用后摆脱所有递归。

由于tuv将保持较小,因此您可以为递归生成完全扩展的代码。为简单起见,假设伪造的

实现
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中。对于TUV的每个不同组合,此函数将使用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包裹的uvVal来调用它,但是:

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

第一次运行将需要一些时间,但是由于使用的方法已经编译,因此运行很快。