【问题标题】:Indexed memory pointer arrays versus indirect integer index arrays in ANSI CANSI C 中的索引内存指针数组与间接整数索引数组
【发布时间】:2012-06-04 00:21:32
【问题描述】:

声明

a[i] += a[j] * a[k];

将在一个可能被执行数千到数百万次的循环中执行数千次。索引ijk,代表随机访问a中的条目,可以通过语句设置

i = i_index[l];
j = j_index[l];
k = k_index[l];

其中lfor 循环的索引。整数数组i_indexj_indexk_index在程序开始时设置,可能会偶尔更改。

内存指针数组是另一种选择。例如

*ap1[l] += *ap2[l] * (*ap3[l]);

其中内存指针数组ap1ap2ap3被预先设置为指向最初由i_indexj_indexk_index数组标识的位置。它们也可能偶尔更改。

第一种方法看起来比第二种方法更简洁,但它似乎更慢,除非有某种方法可以向编译器提供额外的信息。 XCode 中的 GCC 编译器似乎无法提前发现 i_indexj_indexk_indexap1ap2ap3 大部分时间是不变的。有什么方法可以提醒 gcc 编译器以提高性能?

【问题讨论】:

  • 您确定干净的解决方案 较慢吗?
  • 首先要尽可能清晰、直接地编写代码。奇怪的是,之后的更改不会使其更快。
  • 同时编写更简洁的代码使其更易于理解、可维护且不易出错。为了让事情变得更快,首先进行分析。
  • 第二个版本不需要所有这些括号,因为[] 的优先级高于一元* - 所以*ap1[l] += *ap2[l] * *ap3[l]; 很好。
  • 您能否安排代码以使对 a 的访问尽可能线性?这将加快 IMO 的速度。如果没有,请查看一个分析器运行,它会告诉您缓存抖动,然后尝试尽可能减少抖动。

标签: c pointers gcc indexing


【解决方案1】:

您不太可能找到让编译器有效地缓存您在第二版代码中明确计算并保存的第一版代码中的指针值的选项。这是因为编译器需要发出代码来生成和保存一个非常大的数据结构来缓存这些值,这不是典型的编译器行为。

但是,根据您所针对的架构,这可能无关紧要。许多架构都有“间接基址+索引”寻址模式,这将用于访问a[i] += a[j] * a[k]; 中的a,并且在现代内核上,与普通的“间接”寻址相比,它们不会有任何性能损失模式(即 - 一条指令将 i 乘以 a[0] 的大小,将其添加到 a 的基地址并取消引用结果)。了解您的目标架构并查看。

可能可以很好地改进任何一个版本的事情是使用structs 的数组而不是三个单独的数组,以便@987654327 的每个值需要三个值@ 在内存中连续保存:

i = index[l].i;
j = index[l].j;
k = index[l].k;

*ap[l].i += *ap[l].j * *ap[l].k;

这意味着您的代码正在通过indexap 数组进行一次线性遍历,而不是同时进行三个线性遍历,这应该有助于预取器识别您在做什么。

【讨论】:

  • 赋值语句*ap1[l] += *ap2[l] * (*ap3[l]); 只是各种for 循环中的许多类似语句之一,涉及不同的数据数组。处理算法表示对置换数据执行的稀疏矩阵运算,其中数据无法按顺序复制、存储或访问。主要目标是最小化计算开销,因为计算机运行时间可能过多。在这种情况下,减少计算开销总是胜过易于编程和调试。
猜你喜欢
  • 2012-07-11
  • 1970-01-01
  • 1970-01-01
  • 2010-10-28
  • 1970-01-01
  • 2012-08-02
  • 2016-03-19
  • 2014-11-25
  • 2020-06-01
相关资源
最近更新 更多