Julia 朱莉娅不稳定的时间安排
在使用Matlab几年后,我开始学习Julia。我首先实现了一个简单的多项式乘法(没有FFT),以尝试理解类型稳定性的作用。这个项目的很大一部分是对快速多项式乘法器的需求。然而,我有以下的时间安排,我一点也不明白Julia 朱莉娅不稳定的时间安排,julia,Julia,在使用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=
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)
我从这段代码的两次连续运行中得到以下计时。从一次跑步到另一次跑步的时间安排完全不稳定。一般来说,我每次测试得到的计时可以在0.05秒到2秒之间。通常,通过for循环的单个运行的计时都具有类似的计时(如下面的示例所示),但即使这样也不总是如此。偶尔,我也会有一些替代品,比如
.05秒
.05秒
1.9秒
.04s
.05秒
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)
太困惑了 简短回答:您的代码有点奇怪,因此可能会以意外的方式触发垃圾收集,从而导致不同的计时 详细回答:我同意你的时间安排有点奇怪。我不完全确定我能确切地找出问题的原因,但我99%肯定这与垃圾收集有关 因此,您的代码有点奇怪,因为您允许任何维度的输入数组,即使您随后调用
dot
函数(获取两个向量的点积的BLAS例程)。如果您没有意识到,如果您想要一个向量,请使用数组{Float64,1}
,对于矩阵数组{Float64,2}
等等。或者您也可以使用别名向量{Float64}
和矩阵{Float64}
我注意到的第二件奇怪的事情是,在测试中,您生成了rand(1,N)
。这将返回一个数组{Float64,2}
,即矩阵。要获得数组{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
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)
我们得到这样的结果:
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
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)
第一次迭代是测量编译时间,而不是运行时,因此应该忽略(如果您不知道我的意思,那么请查看官方文档的性能提示部分)。正如你所看到的,剩下的迭代要快得多,而且相当稳定。我编辑了原始帖子,试图澄清这种情况。我所说的“运行”是指使用for循环运行较低的块。for循环中的10个计时“通常”都有相似的计时。但是,重新运行这段代码(即,执行10个额外的计时)会给出到处都是的时间。显示的块运行了两次for循环(总共20次计时)。每次运行一次。每组10次计时的每个计时都有相似的值。但是,再次运行该块会给出10个新的计时,其值相差很大(正如您注意到的,上面的两个10块)。我承认这个问题的提问方式有点混乱,但我不知道为什么您被否决为-2。太苛刻了。我可以在我的机器上复制这些结果,这个问题是合理的。当下层选民不在评论中解释他们的行为时,这可能令人沮丧。我正在向上投票,尝试至少将其返回到0。顺便说一句,如果可以保证输入始终是
Array{Float64,2}
,那么您的“good”和“bad”函数将编译为相同的代码,这在您当前的测试中是可以做到的,因此您将看不到两者之间的性能差异。有关更多详细信息,请参阅。感谢科林和埃弗特的宝贵意见。您的解决方案似乎解决了这个特定问题。更大的问题是,我有很多东西要学,多年来,我在Matlab中养成了一些坏习惯,这个简单的例子就证明了这一点。我没有指定数组维度的原因是,这个函数实际上必须执行更高维度的cauchy乘积,因此dot()调用将替换为.*和sum()调用。我想先让1d版本工作。最后一个问题:我一直在使用view(),因为一个介绍Julia的指南说这样做是正确的。这里不是这样吗?@SDK我在2014年从一个全职的Matlab背景来到朱莉娅身边。我会说,我花了一年的时间在朱莉娅全职工作,以训练自己摆脱坏习惯。两个快速提示:1)看看我用向量{@SDK在签名中做了什么是的,在这里使用视图
是一个好主意。视图
在指定的维度上创建一个数组视图,然后可以传递该视图并进行操作