广播分配大量内存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 中的步骤有点不同,但它对 Arrays 做同样的事情):

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.

的方法