广播分配大量内存whist for循环不会
Broadcasting allocates a lot of memory whist for loop doesn't
如果我有以下两种变体来编写我的一部分代码,我通常更喜欢较短的一种。这就是我喜欢 Julia 中的点广播的原因。但是我有这个例子表明它有问题。
function test1(A)
n = size(A, 2)
for k in 1:n
for i in k+1:n
A[i, k] /= A[k, k]
end
end
A
end
function test2(A)
n = size(A, 2)
for k in 1:n
A[k+1:n, k] ./= A[k,k]
end
A
end
A = rand(10000,10000)
B = copy(A)
@allocated test1(B) # returns 0
C = copy(A)
@allocated test2(C) # returns 401131136
C == B # returns true
此外,我试图消除语法糖并多写了一个测试函数
function test3(A)
n = size(A, 2)
for k in 1:n
broadcast!(/, A[k+1:n, k], A[k+1:n, k], A[k,k])
end
A
end
而这个分配的内存是 test2
的两倍,并且... returns 错误的结果。
为什么test3
return 错误的结果,广播有什么问题,有没有说明如何使用它的指南?
问题是在 A[k+1:n, k] ./= A[k,k]
中你可以认为它被重写为:
A[k+1:n, k] .= A[k+1:n, k] ./ A[k,k]
现在的重点是,在 A[k+1:n, k] ./ A[k,k]
部分中, A[k+1:n, k]
部分在执行除法之前复制了数组。原因是安全。您可以通过显式制作视图而不是复制来覆盖它:
function test2_fix(A)
n = size(A, 2)
for k in 1:n
A[k+1:n, k] .= view(A, k+1:n, k) ./ A[k,k]
end
A
end
broadcast!
调用更糟糕且不正确,因为 A[k+1:n, k]
在将值传递给 broadcast!
之前创建了两次副本(因为你使用了两次)所以你的源数组 A
不会受到影响
我将分解语法的最终含义。这是您的第一次广播尝试及其等价物(好吧,Meta.@lower
中的步骤有点不同,但它对 Array
s 做同样的事情):
A[k+1:n, k] ./= A[k,k]
A[k+1:n, k] .= A[k+1:n, k] ./ A[k,k]
broadcast!(/, @view(A[k+1:n, k]), A[k+1:n, k], A[k,k])
请注意如何设置 .=
broadcast!
以将结果写入左侧数组 A
的 视图。视图访问数组的内存,从 v1.5 开始它是非分配的。您对 broadcast!
的第二次尝试错过了这个细节,因此您最终写入了一个新数组而不是 A
.
问题在于 A[k+1:n, k]
等方括号语法会 getindex
。如果 getindex
正在访问 >1 个值,它会分配一个新数组并将值复制到它,因此在这种情况下,您要使用 view
而不是 getindex
。以下是这样做的等效方法:
broadcast!(/, @view(A[k+1:n, k]), @view(A[k+1:n, k]), A[k,k])
A[k+1:n, k] .= @view(A[k+1:n, k]) ./ A[k,k]
@view(A[k+1:n, k]) ./= A[k,k]
@views A[k+1:n, k] ./= A[k,k]
@view
宏允许您使用切片括号语法来调用 view
而不是 getindex
。 @views
宏是一种将以下表达式中的所有切片括号语法转换为使用 view
而不是 getindex
.
的方法
如果我有以下两种变体来编写我的一部分代码,我通常更喜欢较短的一种。这就是我喜欢 Julia 中的点广播的原因。但是我有这个例子表明它有问题。
function test1(A)
n = size(A, 2)
for k in 1:n
for i in k+1:n
A[i, k] /= A[k, k]
end
end
A
end
function test2(A)
n = size(A, 2)
for k in 1:n
A[k+1:n, k] ./= A[k,k]
end
A
end
A = rand(10000,10000)
B = copy(A)
@allocated test1(B) # returns 0
C = copy(A)
@allocated test2(C) # returns 401131136
C == B # returns true
此外,我试图消除语法糖并多写了一个测试函数
function test3(A)
n = size(A, 2)
for k in 1:n
broadcast!(/, A[k+1:n, k], A[k+1:n, k], A[k,k])
end
A
end
而这个分配的内存是 test2
的两倍,并且... returns 错误的结果。
为什么test3
return 错误的结果,广播有什么问题,有没有说明如何使用它的指南?
问题是在 A[k+1:n, k] ./= A[k,k]
中你可以认为它被重写为:
A[k+1:n, k] .= A[k+1:n, k] ./ A[k,k]
现在的重点是,在 A[k+1:n, k] ./ A[k,k]
部分中, A[k+1:n, k]
部分在执行除法之前复制了数组。原因是安全。您可以通过显式制作视图而不是复制来覆盖它:
function test2_fix(A)
n = size(A, 2)
for k in 1:n
A[k+1:n, k] .= view(A, k+1:n, k) ./ A[k,k]
end
A
end
broadcast!
调用更糟糕且不正确,因为 A[k+1:n, k]
在将值传递给 broadcast!
之前创建了两次副本(因为你使用了两次)所以你的源数组 A
不会受到影响
我将分解语法的最终含义。这是您的第一次广播尝试及其等价物(好吧,Meta.@lower
中的步骤有点不同,但它对 Array
s 做同样的事情):
A[k+1:n, k] ./= A[k,k]
A[k+1:n, k] .= A[k+1:n, k] ./ A[k,k]
broadcast!(/, @view(A[k+1:n, k]), A[k+1:n, k], A[k,k])
请注意如何设置 .=
broadcast!
以将结果写入左侧数组 A
的 视图。视图访问数组的内存,从 v1.5 开始它是非分配的。您对 broadcast!
的第二次尝试错过了这个细节,因此您最终写入了一个新数组而不是 A
.
问题在于 A[k+1:n, k]
等方括号语法会 getindex
。如果 getindex
正在访问 >1 个值,它会分配一个新数组并将值复制到它,因此在这种情况下,您要使用 view
而不是 getindex
。以下是这样做的等效方法:
broadcast!(/, @view(A[k+1:n, k]), @view(A[k+1:n, k]), A[k,k])
A[k+1:n, k] .= @view(A[k+1:n, k]) ./ A[k,k]
@view(A[k+1:n, k]) ./= A[k,k]
@views A[k+1:n, k] ./= A[k,k]
@view
宏允许您使用切片括号语法来调用 view
而不是 getindex
。 @views
宏是一种将以下表达式中的所有切片括号语法转换为使用 view
而不是 getindex
.