Math 在Julia中有效地向矩阵添加标量

Math 在Julia中有效地向矩阵添加标量,math,matrix,julia,Math,Matrix,Julia,我需要给一个巨大矩阵的所有元素添加一个标量。矩阵将尽可能大。在本例中,我将使用2 GiB的大小,但在我的实际计算中,它将大得多 A = rand(2^14, 2^14) 如果我执行 A += 1.0 Julia分配额外的2 GiB内存。操作大约需要1s。我可以为循环使用: for jj = 1:size(A, 2), ii = 1:size(A, 1) A[ii, jj] = A[ii, jj] + 1.0 end 这不会分配任何内存,但需要一分钟。这两种方法对我来说都不可行,因为第一

我需要给一个巨大矩阵的所有元素添加一个标量。矩阵将尽可能大。在本例中,我将使用2 GiB的大小,但在我的实际计算中,它将大得多

A = rand(2^14, 2^14)
如果我执行

A += 1.0
Julia分配额外的2 GiB内存。操作大约需要1s。我可以为循环使用

for jj = 1:size(A, 2), ii = 1:size(A, 1)
  A[ii, jj] = A[ii, jj] + 1.0
end

这不会分配任何内存,但需要一分钟。这两种方法对我来说都不可行,因为第一种方法违反了内存限制,而第二种方法显然效率低下。对于元素级乘法,有
scal,它使用BLAS。有没有什么方法可以像使用
scal进行乘法那样高效地执行加法呢

您可以执行就地广播操作:

julia> A = rand(2^14, 2^14); A[1:5, 1:5]
5x5 Array{Float64,2}:
 0.229662  0.680236    0.131202  0.111664   0.802698
 0.500575  0.580994    0.385844  0.983806   0.324382
 0.701694  0.577749    0.532591  0.0508955  0.94325 
 0.592929  0.00319653  0.759241  0.448704   0.706204
 0.867945  0.0413606   0.586151  0.82561    0.679233

julia> @time broadcast!(.+, A, A, 100);
elapsed time: 0.382669486 seconds (11490976 bytes allocated)

julia> A[1:5, 1:5]
5x5 Array{Float64,2}:
 100.23   100.68   100.131  100.112  100.803
 100.501  100.581  100.386  100.984  100.324
 100.702  100.578  100.533  100.051  100.943
 100.593  100.003  100.759  100.449  100.706
 100.868  100.041  100.586  100.826  100.679

它总共只使用了大约2G的内存。

@DSM的答案很好。然而,这里还有一些事情我想补充说明。for循环之所以慢,是因为
A
是一个非常量全局变量,而您的代码直接对该全局变量进行变异。由于
A
是非常量,因此代码必须防止
A
在循环执行过程中的任何时候成为不同类型的不同值。代码必须在循环的每次迭代中查找
A
的类型和位置,并在表达式
A[ii,jj]=A[ii,jj]+1.0
中动态调度方法调用,这是对
getindex
+
setindex的调用
,所有这些都依赖于静态未知类型的
A
。只需在函数中执行此操作,即可立即获得更好的性能:

julia> A = rand(2^10, 2^10);

julia> @time for jj = 1:size(A, 2), ii = 1:size(A, 1)
           A[ii, jj] += 1
       end
elapsed time: 0.288340785 seconds (84048040 bytes allocated, 15.59% gc time)

julia> function inc!(A)
           for jj = 1:size(A, 2), ii = 1:size(A, 1)
               A[ii, jj] += 1
           end
       end
inc! (generic function with 1 method)

julia> @time inc!(A)
elapsed time: 0.006076414 seconds (171336 bytes allocated)

julia> @time inc!(A)
elapsed time: 0.000888457 seconds (80 bytes allocated)
避免像这样的非常量全局变量是本手册章节中的第一条建议。您可能还想阅读本章的其余部分

我们可以进一步提高
inc的性能函数使用
@inbounds
注释指示此代码不需要边界检查,并使用线性索引而不是二维索引:

julia> function inc!(A)
           @inbounds for i = 1:length(A)
               A[i] += 1
           end
       end
inc! (generic function with 1 method)

julia> @time inc!(A)
elapsed time: 0.000637934 seconds (80 bytes allocated)

大多数加速来自
@inbounds
注释,而不是线性索引,尽管这确实会稍微提高速度。然而,应谨慎使用
@inbounds
注释,并且只有在确定索引不能超出范围且性能至关重要的情况下才能使用。正如您所看到的,额外的性能改进虽然存在,但并不是压倒性的。大部分好处来自于不直接改变全局变量。

值得注意的是,
广播使用的大部分时间和内存来自编译;再运行一次,您将看到这两个值都大幅下降。