涉及编译器重排序的优化示例

9

C和C++编译器允许重新排序操作,只要遵守as-if规则。请举一个编译器执行的这样的重新排序的例子,并说明通过这样做可能获得的潜在性能提升。

欢迎使用任何平台上的任何(C / C ++)编译器的示例。


你看过http://preshing.com/20120625/memory-ordering-at-compile-time/吗? - NPE
@NPE 我在这里使用godbolt演示了第一个例子的工作原理:链接 - Shafik Yaghmour
我看过那篇文章,还有其他一些文章,但我找不到编译器为什么会认为这样的排序可能会提高性能的解释。 - TripShock
3个回答

11

假设您正在执行以下操作:

int i=0,j=0;
i++;
i++;
i++;
j++;
j++;
j++;

暂不考虑编译器将三个增量优化为一个+=3的情况,如果你重新排列操作顺序,处理器流水线吞吐量会更高。

i++;
j++;
i++;
j++;
i++;
j++;

由于j++不必等待i++的结果,而在前一种情况下,大多数指令都对前一条指令具有数据依赖性。在更复杂的计算中,如果没有简单的方法来减少要执行的指令数量,编译器仍然可以查看数据依赖关系并重新排序指令,使得依赖于先前指令结果的指令与之尽可能远离。

另一个此类优化的例子是当你处理纯函数时。再次看一个简单的例子,假设你有一个纯函数f(int x),你正在循环中对其求和。

int tot = 0;
int x;//something known only at runtime
for(int i = 0; i < 100; i++)
  tot += f(x);

由于f是一个纯函数,编译器可以随意重新排序对其的调用。特别地,它可以将此循环转换为

int tot = 0;
int x;//something known only at runtime
int fval = f(x);
for(int i = 0; i < 100; i++)
  tot += fval;

你的第一个例子指出重新排序有助于流水线吞吐量。尽管如此,我认为CPU重新排序也可以进行这样的优化。如果是这样,那么编译器为什么要重新排序呢?提前感谢。 - HCSF
@Pradhan 感谢您的回答,只是想提一下纯函数是指对于相同的参数值始终返回相同结果的函数。 - Guy Avraham

5

我相信有很多情况下重新排序操作会带来更快的性能。一个明显的例子是尽早重新排序加载操作,因为这些操作通常比其他CPU操作慢得多。在内存被获取时做其他不相关的工作,CPU可以节省总时间。

也就是说,如果有类似这样的情况:

expensive_calculation();
x = load();
do_something(x);

我们可以像这样重新排序:
x = load();
expensive_calculation();
do_something(x);

因此,当我们等待加载完成时,我们可以免费进行expensive_calculation()


4
假设您有一个循环,如下所示:
for (i=0; i<n; i++) dest[i] = src[i];

想一下 memcpy。您可能希望编译器能够矢量化,即一次加载8或16个字节,然后一次存储8或16个字节。进行此转换是一种重新排序,因为它会导致在存储dest[0]之前读取src[1]。此外,除非编译器知道srcdest不重叠,否则这是一种无效的转换,即编译器不允许进行的转换。使用restrict关键字(C99及更高版本)可以告诉编译器它们不重叠,以便进行这种(非常有价值的)优化。
同样的事情经常在对数组进行操作时出现,而不仅仅是复制 - 例如向量/矩阵操作,声音/图像采样数据的转换等。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接