我使用Matlab几年后开始学习Julia。我开始实现一个简单的多项式乘法(没有FFT)来尝试理解类型稳定性的作用。该项目的一个重要部分是需要快速多项式乘法器。但是,我有以下时间,我根本无法理解。
function cauchyproduct(L::Array{Float64},R::Array{Float64})
# good one for floats
N = length(L)
prodterm = zeros(1,2N-1)
for n=1:N
Lterm = view(L,1:n)
Rterm = view(R,n:-1:1)
prodterm[n] = dot(Lterm,Rterm)
end
for n = 1:N-1
Lterm = view(L,n+1:N)
Rterm = view(R,N:-1:n+1)
prodterm[N+n] = dot(Lterm,Rterm)
end
prodterm
end
testLength = 10000
goodL = rand(1,testLength)
goodR = rand(1,testLength)
for j in 1:10
@time cauchyproduct(goodL,goodR)
end
@which cauchyproduct(goodL,goodR)
我从这段代码的2次连续运行中得到以下时间。从一次奔跑到另一次奔跑的这些时间完全不稳定。一般来说,每次测试的时间范围可以在.05s到2s之间。通常,单个运行for循环的时间都将具有相似的时序(如下例所示),但即使如此,情况也始终如此。偶尔,我有它等候 .05s .05s 1.9s .04s .05s 2.1s时 等等。
知道为什么会这样吗?
0.544795 seconds (131.08 k allocations: 5.812 MiB)
0.510395 seconds (120.00 k allocations: 5.340 MiB)
0.528362 seconds (120.00 k allocations: 5.340 MiB, 0.94% gc time)
0.507156 seconds (120.00 k allocations: 5.340 MiB)
0.507566 seconds (120.00 k allocations: 5.340 MiB)
0.507932 seconds (120.00 k allocations: 5.340 MiB)
0.527383 seconds (120.00 k allocations: 5.340 MiB)
0.513301 seconds (120.00 k allocations: 5.340 MiB, 0.83% gc time)
0.509347 seconds (120.00 k allocations: 5.340 MiB)
0.509177 seconds (120.00 k allocations: 5.340 MiB)
0.052247 seconds (120.00 k allocations: 5.340 MiB, 7.95% gc time)
0.049644 seconds (120.00 k allocations: 5.340 MiB)
0.047275 seconds (120.00 k allocations: 5.340 MiB)
0.049163 seconds (120.00 k allocations: 5.340 MiB)
0.049029 seconds (120.00 k allocations: 5.340 MiB)
0.054050 seconds (120.00 k allocations: 5.340 MiB, 8.36% gc time)
0.047010 seconds (120.00 k allocations: 5.340 MiB)
0.051240 seconds (120.00 k allocations: 5.340 MiB)
0.050961 seconds (120.00 k allocations: 5.340 MiB)
0.049841 seconds (120.00 k allocations: 5.340 MiB, 4.90% gc time)
编辑:显示的时序是通过连续两次执行已定义函数下的代码获得的。具体来说,代码块
goodL = rand(1,testLength)
goodR = rand(1,testLength)
for j in 1:10
@time cauchyproduct(goodL,goodR)
end
在不同的运行中给出了截然不同的时序(没有重新编译它上面的函数)。在所有时间中,都会调用相同的cauchyproduct方法(顶级版本)。希望这能澄清问题所在。
编辑2:我在最后将代码块更改为以下
testLength = 10000
goodL = rand(1,testLength)
goodR = rand(1,testLength)
for j = 1:3
@time cauchyproduct(goodL,goodR)
end
for j = 1:3
goodL = rand(1,testLength)
goodR = rand(1,testLength)
@time cauchyproduct(goodL,goodR)
end
@time cauchyproduct(goodL,goodR)
@time cauchyproduct(goodL,goodR)
@time cauchyproduct(goodL,goodR)
并在2次重复执行新块时得到以下时间。
时间1:
0.045936 seconds (120.00 k allocations: 5.340 MiB)
0.045740 seconds (120.00 k allocations: 5.340 MiB)
0.045768 seconds (120.00 k allocations: 5.340 MiB)
1.549157 seconds (120.00 k allocations: 5.340 MiB, 0.14% gc time)
0.046797 seconds (120.00 k allocations: 5.340 MiB)
0.046637 seconds (120.00 k allocations: 5.340 MiB)
0.047143 seconds (120.00 k allocations: 5.341 MiB)
0.049088 seconds (120.00 k allocations: 5.341 MiB, 3.88% gc time)
0.049246 seconds (120.00 k allocations: 5.341 MiB)
时间2:
2.250852 seconds (120.00 k allocations: 5.340 MiB)
2.370882 seconds (120.00 k allocations: 5.340 MiB)
2.247676 seconds (120.00 k allocations: 5.340 MiB, 0.14% gc time)
1.550661 seconds (120.00 k allocations: 5.340 MiB)
0.047258 seconds (120.00 k allocations: 5.340 MiB)
0.047169 seconds (120.00 k allocations: 5.340 MiB)
0.048625 seconds (120.00 k allocations: 5.341 MiB, 4.02% gc time)
0.045489 seconds (120.00 k allocations: 5.341 MiB)
0.049457 seconds (120.00 k allocations: 5.341 MiB)
太困惑了。
答案 0 :(得分:3)
简短回答:您的代码有点奇怪,因此可能会以意想不到的方式触发垃圾回收,导致时间变化。
答案很长:我同意你得到的时间有点奇怪。我不能完全确定我能确切地确定造成问题的原因,但我99%肯定这与垃圾收集有关。
所以,你的代码有点奇怪,因为你允许任何维度的输入数组,即使你然后调用dot
函数(一个BLAS例程来获取两个向量的点积)。如果您没有意识到,如果您想要一个向量,请使用Array{Float64,1}
,并使用矩阵Array{Float64,2}
,依此类推。或者您也可以使用别名Vector{Float64}
和Matrix{Float64}
。
我注意到的第二个奇怪的事情是,在测试中,您生成rand(1, N)
。这将返回Array{Float64,2}
,即矩阵。要获得Array{Float64, 1}
,即向量,您可以使用rand(N)
。然后在您的函数中,您可以查看大小为1xN的矩阵。现在,Julia使用列主要排序,因此对于向量使用1xN对象将真正低效,并且可能是您奇怪时间的来源。在引擎盖下,我怀疑对dot
的调用将涉及将这些事物转换为浮点数的常规向量,因为dot
最终会反馈到需要此输入类型的基础BLAS例程。所有这些转换都意味着大量的临时存储,需要在某些时候进行垃圾收集,这可能是不同时间的来源(90%的时间,相同代码的不同时间是垃圾的结果)收集器被触发 - 有时以非常意想不到的方式)。
因此,可能有几种方法可以改进以下内容,但我的快速和脏版本的功能如下所示:
function cauchyproduct(L::AbstractVector{<:Number}, R::AbstractVector{<:Number})
length(L) != length(R) && error("Length mismatch in inputs")
N = length(L)
prodterm = zeros(1,2*N-1)
R = flipdim(R, 1)
for n=1:N
prodterm[n] = dot(view(L, 1:n), view(R, N-n+1:N))
end
for n = 1:N-1
prodterm[N+n] = dot(view(L, n+1:N), view(R, 1:N-n))
end
return prodterm
end
注意,我在循环之前翻转R
,因此内存不需要在循环内反复重新排序。毫无疑问,这会导致您的奇怪的垃圾收集问题。然后,应用你的测试(我认为最好在循环中移动数组生成,以防一些聪明的缓存问题抛出时间):
testLength = 10000
for j = 1:20
goodL = rand(testLength);
goodR = rand(testLength);
@time cauchyproduct(goodL,goodR);
end
我们得到这样的东西:
0.105550 seconds (78.19 k allocations: 3.935 MiB, 2.91% gc time)
0.022421 seconds (40.00 k allocations: 2.060 MiB)
0.022527 seconds (40.00 k allocations: 2.060 MiB)
0.022333 seconds (40.00 k allocations: 2.060 MiB)
0.021568 seconds (40.00 k allocations: 2.060 MiB)
0.021837 seconds (40.00 k allocations: 2.060 MiB)
0.022155 seconds (40.00 k allocations: 2.060 MiB)
0.022071 seconds (40.00 k allocations: 2.060 MiB)
0.021720 seconds (40.00 k allocations: 2.060 MiB)
0.024774 seconds (40.00 k allocations: 2.060 MiB, 9.13% gc time)
0.021714 seconds (40.00 k allocations: 2.060 MiB)
0.022066 seconds (40.00 k allocations: 2.060 MiB)
0.021815 seconds (40.00 k allocations: 2.060 MiB)
0.021819 seconds (40.00 k allocations: 2.060 MiB)
0.021928 seconds (40.00 k allocations: 2.060 MiB)
0.021795 seconds (40.00 k allocations: 2.060 MiB)
0.021837 seconds (40.00 k allocations: 2.060 MiB)
0.022285 seconds (40.00 k allocations: 2.060 MiB)
0.021380 seconds (40.00 k allocations: 2.060 MiB)
0.023828 seconds (40.00 k allocations: 2.060 MiB, 6.91% gc time)
第一次迭代是测量编译时间而不是运行时间,所以应该忽略(如果你不知道我的意思,那么请查看官方文档的性能提示部分)。如您所见,剩余的迭代速度更快,而且非常稳定。