在 Julia 中为数学常数供电很慢

Powering a Math constant in Julia is slow

我很确定这一定是一个错误...

好吧,基本上,当我将一个数学常量类型传递给幂 (^) 函数并迭代它几次时...循环真的很慢并且使用了大量内存分配。这个例子很简单,因为它不会产生任何结果,但我有实际的应用程序,但我遇到了这个问题。

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

这是用大 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)

这是一个错误吗?或者对此有合理的解释吗?在大循环中将 pi 驱动到第 4 位的正确方法是什么?

谢谢。

查看 Julia 0.3 和 0.4 中的 Constants.jl

虽然看起来你使用的是 Julia 0.3,而且这些结果主要是关于 Julia 0.4,但它也有同样的问题,原因是相同的,这是不必要的中间体分配。

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

朱莉娅 0.3 与朱莉娅 0.4

Julia 0.3 中的第一次

julia> @time test1(10000000)
elapsed time: 0.313772825 seconds (320393016 bytes allocated, 37.16% gc time)

在 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)

当分配数量异常时,总是怀疑类型不稳定或临时。

pi 的 ^ 定义

特别是,lines 70 through 72 定义了一个通用方法来提高 MathConst 的幂。

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

使用 ^ 代替 pi

另请注意,稍后是一个specialization for the constant e,将pi替换为e的新测试不会出现同样的问题。

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

运行 测试4

julia> @time test4(10000000)
 108.401 milliseconds (4 allocations: 144 bytes)

Int 到 Float64 的转换

如果 Float 是从 Int 创建的,那么这将通过从 开始来避免这个问题浮动.

julia> function test5(N)
           for i = 1:N
               x = pi^4.0
           end
       end

这是怎么回事

julia> @time test5(10000000)
  65.430 milliseconds (4 allocations: 144 bytes)

定义一组新函数

最后,可以创建一组新函数(在这种情况下进行测试),专门将 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.

然后重新创建test1

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

然后再运行

julia> @time test1(10000000)
   2.757 microseconds (4 allocations: 144 bytes)

对 Julia 0.3 做同样的事情

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

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

最后在 Julia 0.3

中重新定义和重新运行 测试
julia> @time test1(10000000)
elapsed time: 3.023e-6 seconds (80 bytes allocated)