【问题标题】:iteration direction on an array数组的迭代方向
【发布时间】:2014-07-21 06:35:33
【问题描述】:

假设我们有两个基本类型的数组ab(比如float),我们需要为每个有效索引i 计算a[i] + b[i],并存储结果。迭代数组以最大化缓存命中的最佳方法是什么?是从前到后、从后到前还是其他?

【问题讨论】:

  • 首先,不要使用普通数组,如果您知道编译时的大小,请使用std::array,或者使用std::vector
  • 我会说,以“最正常的方式”对您的迭代进行编码,并让编译器完成它的工作;)
  • 如果你关心这个,你应该设置一些基准。
  • 在现代 CPU 上,差异通常可以忽略不计,编译器在优化方面通常比您更聪明。除非你有一个事实原因(即你已经分析了你的代码并得出你有瓶颈的结论),否则你不应该关心这个。
  • @StefanoSanfilippo 我正在使用索引技巧来消除 for/while 循环,但我的编译器不够聪明,无法弄清楚我在做什么实际上是一个展开的循环。

标签: c++ arrays cpu-cache


【解决方案1】:

对于这种操作,您应该使用编译器的自动矢量化。将小 i 迭代到大 i。此外,答案取决于“存储结果”的含义以及您要迭代的项目数量n

如果您的意思是 c[i] = a[i] + b[i]n 不是太小,那么您的编译器的自动矢量化器将对此进行最佳优化,而无需进行任何更改。即使是 MSVC 也会得到正确的结果(至少对于 SSE 而言)。您的编译器将不得不对 n 进行一些调整,而不是 4 的倍数(或 8 对于 AVX)和对齐,但此成本将在 n 中分摊,并且此开销的影响可以忽略不计,除了小的 n。如果n 很小,那么您可能需要考虑对齐。小到多小必须确定,但我猜它远小于 100。

如果您的意思是sum + = a[i] + b[i],减少,那么您确实需要考虑这一点。这有一个依赖链,所以你需要展开你的循环3-10 times。此外,由于floating point arithmetic is not associative and the auto-vectorization won't kick in without it,您需要使用宽松的浮点模型,因此将-ffast-math 添加到GCC(/fp:fast 到MSVC)。如果您展开循环并使用宽松的浮点模型,那么 GCC、ICC、Clang 和 MSVC 应该可以有效地自动矢量化您的归约。

【讨论】:

  • 如果您“无限”展开,即完全消除循环,会发生什么?
  • @user1095108,取决于你在做什么。如果 n 真的很小,那么完全展开可能是有意义的。你必须尝试看看。请记住代码缓存的大小 (32kB)。如果每个 CPU 集的正确优化发生变化,请不要感到惊讶。
【解决方案2】:

为了利用缓存预取功能,您需要从前到后顺序读取数组。

此外,数组应该是 SSE 对齐的(16 字节)。更重要的是项目(例如浮点数)将根据它们的大小对齐(浮点数为 4 个字节)。这很重要,因此数据不会跨缓存行(读取速度较慢)。

数组对齐后,您可以使用 SSE/AVX 读取、添加和存储在单个指令中执行 4 或 8 次操作的结果。

编辑: 您可以在here 中阅读有关缓存预取的更多信息以及在Intel SW Developer Manual 中的深入描述。

【讨论】:

  • 请注意,如果可能,编译器(至少是 GCC 和 Clang)可能会使用 SSE 指令集进行优化。
  • 当然取决于优化级别。因此对齐的重要性。
  • @egur 你能提供从前到后经验法则的来源吗?我什至可以随机排序。
  • 从前到后表示从数组索引零开始,依次向上移动。 2 微不足道的源代码...
  • 根据英特尔® 64 和 IA-32 架构优化参考手册(2011 年 6 月)第 2.2.4.3 节预取逻辑,Core 微架构的流预取器同时支持转发和后向流,尽管有 12 个前向条目和只有 4 个后向条目(每个条目覆盖一个 4KiB 页面);很明显,前向流是首选,但也支持后向流。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-12-12
  • 1970-01-01
  • 2012-01-31
  • 2010-11-21
  • 2012-11-12
  • 2023-04-06
  • 2018-08-02
相关资源
最近更新 更多