我很好奇为什么Julias执行矩阵添加似乎会复制。下面是一个例子:
foo1=rand(1000,1000)
foo2=rand(1000,1000)
foo3=rand(1000,1000)
julia> @time foo1=foo2+foo3;
0.001719 seconds (9 allocations: 7.630 MB)
julia> sizeof(foo1)/10^6
8.0
分配的内存量与这些维度矩阵所需的内存大致相同。
看起来为了处理foo2 + foo3,内存被分配用于存储结果,然后foo1通过引用分配给它。
这是否意味着对于大多数线性代数运算,我们需要直接调用BLAS和LAPACK函数来执行操作?
答案 0 :(得分:13)
要了解这里发生了什么,让我们考虑foo1 = foo2 + foo3
实际上做了什么。
foo2 + foo3
。为此,它将分配一个 new 临时数组来保存输出foo1
绑定到这个新的临时数组,撤消您为预先分配输出数组而付出的所有努力。 简而言之,您会看到内存使用情况与结果数组的内存使用情况有关,因为例程确实为该大小的数组分配了新内存。
以下是一些替代方案:
broadcast!
copy!(foo1, foo2+foo3)
然后你预先分配的数组将被填充,但它仍会分配临时(见下文)以下是这4个案例的一些代码
julia> function with_loop!(foo1, foo2, foo3)
for i in eachindex(foo2)
foo1[i] = foo2[i] + foo3[i]
end
end
julia> function with_broadcast!(foo1, foo2, foo3)
broadcast!(+, foo1, foo2, foo3)
end
julia> function with_copy!(foo1, foo2, foo3)
copy!(foo1, foo2+foo3)
end
julia> function original(foo1, foo2, foo3)
foo1 = foo2 + foo3
end
现在让我们来看看这些功能
julia> for f in [:with_broadcast!, :with_loop!, :with_copy!, :original]
@eval $f(foo1, foo2, foo3) # compile
println("timing $f")
@eval @time $f(foo1, foo2, foo3)
end
timing with_broadcast!
0.001787 seconds (5 allocations: 192 bytes)
timing with_loop!
0.001783 seconds (4 allocations: 160 bytes)
timing with_copy!
0.003604 seconds (9 allocations: 7.630 MB)
timing original
0.002702 seconds (9 allocations: 7.630 MB, 97.91% gc time)
您可以看到with_loop!
和broadcast!
做同样的事情,两者都比其他人更快,更有效率。 with_copy!
和original
速度较慢且占用内存较多。
一般来说,要进行实地操作,我建议先写一个循环
答案 1 :(得分:2)
首先,阅读@ spencerlyon2的回答。另一种方法是使用Dahua Lin的包Devectorize.jl
。它定义了@devec
宏,它自动将向量(数组)表达式转换为循环代码。
在此示例中,我们将如下定义with_devec!(foo1,foo2,foo3)
,
julia> using Devectorize # install with Pkg.add("Devectorize")
julia> with_devec!(foo1,foo2,foo3) = @devec foo1[:]=foo2+foo3
运行基准测试可以实现4个分配结果。
答案 2 :(得分:0)
您可以使用LinearAlgebra软件包中的axpy!
函数。
using LinearAlgebra
julia> @time BLAS.axpy!(1., foo2, foo3)
0.002187 seconds (4 allocations: 160 bytes)