【问题标题】:Why does array += (without @.) produce so much memory allocation?为什么数组+=(没有@.)会产生这么多的内存分配?
【发布时间】:2017-12-30 17:19:02
【问题描述】:

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

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

【问题讨论】:

  • 克里斯对您的问题给出了出色而彻底的回答。有帮助吗?

标签: julia


【解决方案1】:

total += atotal = 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 的引用设置为这个新数组。这就是为什么向量化操作比传统的低级循环慢的原因,也是使用 NumPy 之类的东西可以比 Python 更快但不能完全达到 C 的主要原因之一(因为这些临时性!)。

@. total += atotal .= total .+ a 相同。 This blog post 解释说,在 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

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

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

【讨论】:

  • @. 是一个宏,用于将 . 广播应用于整个表达式或代码块的运算符,请注意,在您的简单示例中,@. total += a 等于total .+= a 正如 Chris 进一步解释的那样,与 total .= total .+ a
猜你喜欢
  • 2021-04-18
  • 2012-11-08
  • 2017-05-03
  • 2021-04-16
  • 1970-01-01
  • 2020-10-26
  • 1970-01-01
  • 2014-10-19
  • 2011-01-17
相关资源
最近更新 更多