【问题标题】:Fast indexing of arrays快速索引数组
【发布时间】:2015-12-02 08:20:56
【问题描述】:

在大型多维数组中访问(并且可能替换)条目的最有效方法是什么?我在循环中使用这样的东西:

tup = (16,45,6,40,3)
A[tup...] = 100

但我想知道是否有更有效的方法。特别是,有没有办法可以避免使用...

【问题讨论】:

  • 对于特定条目,您可以预先计算ii=sub2ind(size(A),tup...),然后使用A[ii]
  • 我相信这与A[tup...]进行完全相同的计算
  • 它正在执行相同的计算,但 预计算 一次,并将计算保存在循环中(这可能是对问题的一种解释)

标签: arrays indexing julia


【解决方案1】:

喷溅并不总是会受到惩罚,但确定喷溅的效率并不总是显而易见(或容易)。您的简单示例实际上与编写 A[16,45,6,40,3] = 100 一样有效。对比一下就知道了

   function f(A)
       tup = (16,45,6,40,3)
       A[tup...] = 100
       A
   end
   function g(A)
       A[16,45,6,40,3] = 100
       A
   end
julia> code_llvm(f, Tuple{Array{Int, 5}})
# Lots of output (bounds checks).
julia> code_llvm(g, Tuple{Array{Int, 5}})
# Identical to above

如果有喷溅处罚,您会以分配的形式看到它。您可以使用@allocated 宏或简单地检查code_llvm 以获取对@jl_pgcstack 的引用来对此进行测试——这就是垃圾收集器,只要有分配就需要它。请注意,在一个更复杂的函数中很可能还有其他事情也会导致分配,因此它的存在并不一定意味着存在喷溅的悲观情绪。但是,如果这是在一个热循环中,您希望最小化所有分配,所以这是一个很好的目标……即使您的问题不是由于飞溅造成的。您还应该使用@code_warntype,因为类型不佳的代码肯定会影响 splats 和许多其他操作。如果您的元组类型不正确,会发生以下情况:

   function h(A)
       tup = ntuple(x->x+1, 5) # type inference doesn't know the type or size of this tuple
       A[tup...] = 100
       A
   end
julia> code_warntype(h, Tuple{Array{Int,5}})
# Lots of red flags

因此,优化此 splat 将高度依赖于您如何构建或获取 tup

【讨论】:

  • 请注意,我根本不谈论索引本身。那是因为实际的索引非常快。对于普通数组,使用全维索引(例如,5 个索引)和线性索引(只有 1 个)索引之间的最大区别是五个边界检查。如果您绝对确定访问是安全的,则可以使用@inbounds 避免这些问题。对于特殊的数组类型(如 SubArrays),避免线性索引会明显更快。
  • 我想我的问题是是否有一种从元组转换为索引的有效方法。对于我的问题,我将永远不必访问SubArrays,只需访问完整Array 的特定元素。我记得在某处读到 splatting 效率不高(也许对于 Julia 的早期版本)。加上@code_warntype 不适用于 splat。
  • @amrods - 我的意思是,对于您提供的示例,使用 splat 将元组解包到单个索引绝对不会涉及额外的开销。您也可以将元组包装在CartesianIndex(tup) 中,但我认为没有开销的情况将与喷出元组没有开销的情况重叠。
【解决方案2】:

要遍历多维数组,推荐使用for index in eachindex(A);参见例如

https://groups.google.com/forum/#!msg/julia-users/CF_Iphgt2Wo/V-b31-6oxSkJ

如果A 是标准数组,那么这对应于仅使用单个整数进行索引,这是访问数组的最快方式(您的原始问题):

A = rand(3, 3)

for i in eachindex(A)
   println(i)
end

但是,如果 A 是一个更复杂的对象,例如一个子数组,然后eachindex(A) 会给你一个不同的、高效的访问对象:

julia> for i in eachindex(slice(A, 1:3, 2:3))
           println(i)
       end

给予

CartesianIndex{2}((1,1))
CartesianIndex{2}((2,1))

等等

【讨论】:

  • 嗯,我可能应该订阅那些公共服务公告...但是,eachindex 将为A 中的每个职位提供一个迭代器。我想我只需要一种从元组转换为索引的方法。
  • 如果你也想要一个线性索引,最简单的就是k = 0; for i in eachindex(A); k += 1; blah blah,现在你有一个线性索引和一个笛卡尔索引。
【解决方案3】:

索引多维数组的最快方法是对其进行线性索引。
Base.Base.linearindexing 是来自Base 模块的相关函数,用于查找最有效地访问数组元素的方式 .

julia> a=rand(1:10...);
julia> Base.Base.linearindexing(a)
Base.LinearFast()

可以使用ii=sub2ind(size(A),tup...) 语法将索引元组转换为一个线性索引或for i in eachindex(A) 来遍历它。

【讨论】:

  • 对于许多数组类型,线性索引绝对不是索引到它们的最快方法。如果你已经有一个多维索引元组,用它索引的最有效方法是……直接用它索引!如果需要,标准库会将其转换为线性索引。但这可能没有必要,即使是这样,如果这不是最快的方法,这也是一个性能错误。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-07-22
  • 2013-09-06
  • 2021-01-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多