我不明白为什么数组的+ =操作会产生如此多的内存分配,但在应用@时会得到修复。
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()
产生
160000 total += a
和
0 @. total += a
答案 0 :(得分:8)
total += a
与total = a + total
相同,out = similar(a)
for i in eachindex(a)
out[i] = total[i] + a[i]
end
total = out
是一个矢量化操作,如:
total = +(total,a)
因为内部是
=
这就像MATLAB,Python或R一样,因此有一个为矢量化操作分配的临时数组,然后total
将@. total += a
的引用设置为这个新数组。这就是为什么矢量化操作比传统的低级循环慢的原因之一,也是使用像NumPy这样的东西比Python更快但不能完全达到C的主要原因之一(因为这些临时值!)。
total .= total .+ a
与# 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
相同。 This blog post解释说,在Julia中,通过匿名函数进行语义点融合,因此对应于执行以下操作:
total
在不创建临时数组的情况下就地更新broadcast!
。
Julia中的Fusion在语义上发生:将点操作转换为匿名函数加上broadcast!
调用(本质上是我在那里编写的循环)是在解析时完成的,并且编译匿名函数以便这是有效的方式。由于其他原因,这也非常有用。通过在通用f!
上重载{{1}},GPUArrays.jl之类的内容可以自动构建在GPU上进行就地更新的高效单内核。这与MATLAB,Python和R相反,其中不同的向量化函数被视为不同的函数调用,因此必须计算返回值,因此必须计算临时数组。