为什么array + =(没有@.)会产生如此多的内存分配?

fra*_*rik 4 julia

我不明白为什么数组的+ =操作会产生如此多的内存分配,但在应用@时它会得到修复.

function loop()
    a = randn(10)
    total = similar(a)

    for i=1:1000
        total += a
    end
end

function loopdot()
    a = randn(10)
    total = similar(a)

    for i=1:1000
        @. total += a
    end
end


loop()
loopdot()

Profile.clear_malloc_data()

loop()
loopdot()
Run Code Online (Sandbox Code Playgroud)

产生

160000         total += a
Run Code Online (Sandbox Code Playgroud)

0         @. total += a
Run Code Online (Sandbox Code Playgroud)

Chr*_*kas 8

total += atotal = a + total矢量化操作相同,如:

out = similar(a)
for i in eachindex(a)
  out[i] = total[i] + a[i]
end
total = out
Run Code Online (Sandbox Code Playgroud)

从内部开始

total = +(total,a)
Run Code Online (Sandbox Code Playgroud)

这就像MATLAB,Python或R,因此有一个为矢量化操作分配的临时数组,然后=设置total这个新数组的引用.这就是为什么矢量化操作比传统的低级循环慢的原因之一,也是使用像NumPy这样的东西比Python更快但不能完全达到C的主要原因之一(因为这些临时代码!).

@. total += a是一样的total .= total .+ a.这篇博文解释说,在Julia中,通过匿名函数进行语义点融合,因此对应于执行以下操作:

# Build an anonymous function for the fused operation
f! = (a,b,c) -> (a[i] = b[i] + c[i])
# Now loop it, since it's `.=` don't make a temporary
for i in eachindex(a)
  f!(total,total,a)
end
Run Code Online (Sandbox Code Playgroud)

total在没有创建临时数组的情况下就地更新.

Julia中的融合在语义上发生:将点操作转换为匿名函数加上broadcast!调用(本质上是我在那里编写的循环)是在解析时完成的,并且编译匿名函数以便这样做有效.由于其他原因,这也非常有用.通过broadcast!在泛型上重载f!,这就像GPUArrays.jl这样的东西自动构建高效的单个内核,这些内核在GPU上进行就地更新.这与MATLAB,Python和R相反,其中不同的向量化函数被视为不同的函数调用,因此必须计算返回值,因此必须计算临时数组.