在Julia中为数学常数提供动力很慢

Est*_*ban 6 constructor constants julia

我很确定这必须是一个bug ...

好吧,基本上,当我将一个Math Constant类型传递给power(^)函数并多次迭代时......循环非常慢并且使用了大量的内存分配.这个例子是微不足道的,因为它不会产生任何结果,但我有真正的应用程序,我有这个问题.

function test1(N)
    for i = 1:N
        x = pi^4
    end
end

function test2(N)
    for i = 1:N
        x = pi*pi*pi*pi
    end
end

function test3(N)
    pif = float64(pi)
    for i = 1:N
        x = pif^4
    end
end
Run Code Online (Sandbox Code Playgroud)

这是用大N调用函数的结果:

@time test1(10000000)
@time test2(10000000)
@time test3(10000000)

elapsed time: 0.958278949 seconds (320000160 bytes allocated, 56.44% gc time)
elapsed time: 6.341e-6 seconds (80 bytes allocated)
elapsed time: 4.982e-6 seconds (80 bytes allocated)
Run Code Online (Sandbox Code Playgroud)

这是一个错误吗?或者它有合理的解释吗?在大循环中为pi提供动力的正确方法是什么?

谢谢.

waT*_*eim 4

查看 Julia 0.3 和 0.4 中的 Constants.jl

虽然看起来您使用的是 Julia 0.3,并且这些结果主要与 Julia 0.4 有关,但它也存在相同的问题,原因相同,都是不必要的中间体分配。

要了解发生了什么,您可以键入methods(^),它将生成为实现 ^ 定义的所有方法的列表。它们有很多,所以我不会列出它们,但重要的是列出了实现它们的 Julia 文件,在本例中,它是Constants.jl

朱莉娅 0.3 与朱莉娅 0.4

第一次在朱莉娅0.3

julia> @time test1(10000000)
elapsed time: 0.313772825 seconds (320393016 bytes allocated, 37.16% gc time)
Run Code Online (Sandbox Code Playgroud)

在 Julia 0.4 中,由于有了更好的垃圾收集器,这个问题有所减少,但仍然存在。

julia> @time test1(10000000)
 170.445 milliseconds (20000 k allocations: 305 MB, 6.94% gc time)

julia> @time test2(10000000)
   2.355 microseconds (4 allocations: 144 bytes)
Run Code Online (Sandbox Code Playgroud)

当分配数量异常时,始终怀疑类型不稳定或临时性。

pi 的 ^ 定义

特别地,第 70 行到第 72 行定义了用于对 MathConst 求幂的通用方法。

for op in Symbol[:+, :-, :*, :/, :^]
    @eval $op(x::MathConst, y::MathConst) = $op(Float64(x),Float64(y))
end
Run Code Online (Sandbox Code Playgroud)

使用 ^ 代替 e 而不是 pi

另请注意,稍后是常量 e 的专门化,并且将 pi 替换为 e 的新测试不会出现同样的问题。

julia> function test4(N)
           for i = 1:N
               x = e^4
           end
       end
test4 (generic function with 1 method)
Run Code Online (Sandbox Code Playgroud)

运行测试4

julia> @time test4(10000000)
 108.401 milliseconds (4 allocations: 144 bytes)
Run Code Online (Sandbox Code Playgroud)

Int 到 Float64 的转换

如果从Int创建Float ,那么从Float开始可以避免该问题。

julia> function test5(N)
           for i = 1:N
               x = pi^4.0
           end
       end
Run Code Online (Sandbox Code Playgroud)

这就是发生的事情

julia> @time test5(10000000)
  65.430 milliseconds (4 allocations: 144 bytes)
Run Code Online (Sandbox Code Playgroud)

定义一组新函数

最后,可以创建一组新函数(在本例中进行测试),专门将 Int 定义为第二个参数以避免强制转换。这不是最好的方法,因为它引入了歧义,但它适合测试。

julia> for op in Symbol[:+, :-, :*, :/, :^]
           @eval $op(x::MathConst, y::Int64) = $op(Float64(x),y)
       end

Warning: New definition 
    ^(MathConst{sym}, Int64) at none:2
is ambiguous with: 
    ^(MathConst{:e}, Integer) at constants.jl:122.
To fix, define 
    ^(MathConst{:e}, Int64)
before the new definition.
Run Code Online (Sandbox Code Playgroud)

然后重新创建test1

julia> function test1(N)
           for i = 1:N
               x = pi^4
           end
       end
test1 (generic function with 1 method)
Run Code Online (Sandbox Code Playgroud)

然后重新运行

julia> @time test1(10000000)
   2.757 microseconds (4 allocations: 144 bytes)
Run Code Online (Sandbox Code Playgroud)

对 Julia 0.3 做同样的事情

相同的修复,但使用float64而不是Float64

julia> for op in Symbol[:+, :-, :*, :/, :^]
          @eval $op(x::MathConst, y::Int64) = $op(float64(x),y)
       end
Run Code Online (Sandbox Code Playgroud)

最后在 Julia 0.3 中重新定义并重新运行测试

julia> @time test1(10000000)
elapsed time: 3.023e-6 seconds (80 bytes allocated)
Run Code Online (Sandbox Code Playgroud)