Performance 朱莉娅为什么要分配这么多内存?
我正试图写一个快速坐标下降算法来解决普通最小二乘回归。下面的Julia代码可以工作,但我不明白它为什么要分配这么多内存Performance 朱莉娅为什么要分配这么多内存?,performance,memory,optimization,garbage-collection,julia,Performance,Memory,Optimization,Garbage Collection,Julia,我正试图写一个快速坐标下降算法来解决普通最小二乘回归。下面的Julia代码可以工作,但我不明白它为什么要分配这么多内存 function OLS_cd{T<:Float64}(A::Array{T,2}, b::Array{T,1}, tolerance::T=1e-12) N,P = size(A) x = zeros(P) r = copy(b) d = ones(P) while sum(d.*d) > tolerance
function OLS_cd{T<:Float64}(A::Array{T,2}, b::Array{T,1}, tolerance::T=1e-12)
N,P = size(A)
x = zeros(P)
r = copy(b)
d = ones(P)
while sum(d.*d) > tolerance
@inbounds for j = 1:P
d[j] = sum(A[:,j].*r)
x[j] += d[j]
r -= d[j]*A[:,j]
end
end
return(x)
end
使用@benchmark OLS\u cd(X,y)
我得到
BenchmarkTools.Trial:
memory estimate: 65.94 mb
allocs estimate: 151359
--------------
minimum time: 19.316 ms (16.49% GC)
median time: 20.545 ms (16.60% GC)
mean time: 22.164 ms (16.24% GC)
maximum time: 42.114 ms (10.82% GC)
--------------
samples: 226
evals/sample: 1
time tolerance: 5.00%
memory tolerance: 1.00%
随着p
变得越来越大,OLS问题变得越来越难,我注意到,随着我把p变大,需要运行更长的时间,Julia分配的内存越多
为什么每个循环都要通过,而循环分配更多内存?在我看来,似乎我的所有操作都已到位,并且类型已明确指定
在评测时,我没有看到任何东西,但如果有用的话,我也可以发布输出
更新:
正如下面指出的,使用矢量化操作导致的临时数组是罪魁祸首。以下操作消除了无关的分配,运行速度非常快:
function OLS_cd_unrolled{T<:Float64}(A::Array{T,2}, b::Array{T,1}, tolerance::T=1e-12)
N,P = size(A)
x = zeros(P)
r = copy(b)
d = ones(P)
while norm(d,Inf) > tolerance
@inbounds for j = 1:P
d[j] = 0.0; @inbounds for i = 1:N d[j] += A[i,j]*r[i] end
@inbounds for i = 1:N r[i] -= d[j]*A[i,j] end
x[j] += d[j]
end
end
return(x)
end
函数OLS\u cd\u展开{T公差
@j=1:P的内边界
d[j]=0.0;i=1的@inbounds:nd[j]+=A[i,j]*r[i]端
@i=1的内边界:nR[i]=d[j]*A[i,j]端
x[j]+=d[j]
终止
终止
返回(x)
终止
A[:,j]
创建的是副本,而不是视图。您想使用@view A[:,j]
或视图(A,:,j)
你可以用r.=-.(r,d[j]*A[:,j]
对r-.(r,d[j]*A[:.j])
进行分解,以去掉更多的临时变量。正如@LutfullahTomak所说的sum(A[:,j].*r)
应该将分解为dot(视图(A,:,j),r)
删除其中的所有临时变量。要使用中缀运算符,可以使用\cdot
,如视图(A,:,j)中所示⋅r
您应该阅读副本与视图以及向量化如何导致临时数组。最重要的是,当发生向量化操作时,它们必须创建一个新的向量作为输出。相反,您希望写入现有的向量。r=…
对于数组更改引用,对于某些生成临时数组的表达式,r=ex
数组将创建一个新数组,然后将r
指向该数组。r.=ex
将用表达式中的值替换数组r
的值。前者分配一个临时值,后者不分配。重复应用此思想是所有临时值的来源。a[:,j]
创建一个副本,而不是视图。您想使用@view a[:,j]
或视图(a,:,j)
你可以用r.=-.(r,d[j]*A[:,j]
对r-.(r,d[j]*A[:.j])
进行分解,以去掉更多的临时变量。正如@LutfullahTomak所说的sum(A[:,j].*r)
应该将分解为dot(视图(A,:,j),r)
删除其中的所有临时变量。要使用中缀运算符,可以使用\cdot
,如视图(A,:,j)中所示⋅r
您应该阅读副本与视图以及向量化如何导致临时数组。最重要的是,当发生向量化操作时,它们必须创建一个新的向量作为输出。相反,您希望写入现有的向量。r=…
对于数组更改引用,对于某些生成临时数组的表达式,r=ex
数组将创建一个新数组,然后将r
指向该数组。r.=ex
将用表达式中的值替换数组r
的值。前者分配一个临时值,后者不分配。重复应用此思想是所有临时值的来源。实际上,sum(d.*d)
,sum(A[:,j].*r)
等都不在适当的位置,并生成临时数组..首先,sum(d.*d)==dot(d,d)
我认为,sum(A[:,j].*r)
生成两个临时数组。对于后者,我要做dot(view(A,:,j),r
。当前稳定版本的julia(0.5)没有r-=d[j]*A[:,j]的简短版本
所以你需要把它分解成一个循环。实际上,sum(d.*d)
,sum(a[:,j].*r)
等等都不在合适的位置,所以你需要做一个临时数组。首先,sum(d.*d)==dot(d,d)
我想和sum(a[:,j].*r)
做两个临时数组
用于后者。当前稳定版本的julia(0.5)没有r-=d[j]*A[:,j]
的短版本,因此您需要将其开发成一个循环。我认为=
适用于0.6,但令我惊讶的是,它在0.5中工作。我做了r.=(*)(d[j],view(A,:,5))
。我认为应该是(…)
对于广播(-…)
我认为括号是没有必要的。但是作为线程的参考,
对于+
的融合,所有这些都将在周一合并。因此几天后它将出现在夜间(但是如果你对Julia不是很有经验,我不建议使用它)我无法使用视图方法,但将矢量化赋值展开到for循环中似乎就可以了。如果我删除-
周围的括号,错误:语法:数值常量“-.”不能隐式相乘,因为它以“
结尾。此外,我在它应该是r.=(-)之前输入了一个错误。(r,(*)(d[j],view(A,:,j))
是的,Julia版本0.5.0 Commit 3c9d753(2016-09-19 18:14 UTC)。sum->dot工作得很好,但是view的东西似乎不起作用。如果代码被展开的循环淹没了,我可能会再次研究它。我原以为
适用于0.6,但令我惊讶的是在0.5下工作的。我确实是r.=(),(*)(d[j],view(A,:,5))
。我认为它应该是(-)
用于广播(…)
我不认为括号是必要的。但是作为线程的参考,
用于+
的融合,所有这些都将在周一进行合并,所以它将在几天后的晚上出现(但如果你对朱莉娅不是很有经验,我不建议你使用它)
function OLS_cd_unrolled{T<:Float64}(A::Array{T,2}, b::Array{T,1}, tolerance::T=1e-12)
N,P = size(A)
x = zeros(P)
r = copy(b)
d = ones(P)
while norm(d,Inf) > tolerance
@inbounds for j = 1:P
d[j] = 0.0; @inbounds for i = 1:N d[j] += A[i,j]*r[i] end
@inbounds for i = 1:N r[i] -= d[j]*A[i,j] end
x[j] += d[j]
end
end
return(x)
end