Performance 朱莉娅为什么要分配这么多内存?

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

我正试图写一个快速坐标下降算法来解决普通最小二乘回归。下面的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
        @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