Julia 朱莉娅不稳定的时间安排

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=

在使用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)
我从这段代码的两次连续运行中得到以下计时。从一次跑步到另一次跑步的时间安排完全不稳定。一般来说,我每次测试得到的计时可以在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在签名中做了什么是的,在这里使用
视图
是一个好主意。
视图
在指定的维度上创建一个数组视图,然后可以传递该视图并进行操作