C和C++编译器允许重新排序操作,只要遵守as-if规则。请举一个编译器执行的这样的重新排序的例子,并说明通过这样做可能获得的潜在性能提升。
欢迎使用任何平台上的任何(C / C ++)编译器的示例。
C和C++编译器允许重新排序操作,只要遵守as-if规则。请举一个编译器执行的这样的重新排序的例子,并说明通过这样做可能获得的潜在性能提升。
欢迎使用任何平台上的任何(C / C ++)编译器的示例。
假设您正在执行以下操作:
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操作慢得多。在内存被获取时做其他不相关的工作,CPU可以节省总时间。
也就是说,如果有类似这样的情况:
expensive_calculation();
x = load();
do_something(x);
x = load();
expensive_calculation();
do_something(x);
因此,当我们等待加载完成时,我们可以免费进行expensive_calculation()
。
for (i=0; i<n; i++) dest[i] = src[i];
memcpy
。您可能希望编译器能够矢量化,即一次加载8或16个字节,然后一次存储8或16个字节。进行此转换是一种重新排序,因为它会导致在存储dest[0]
之前读取src[1]
。此外,除非编译器知道src
和dest
不重叠,否则这是一种无效的转换,即编译器不允许进行的转换。使用restrict
关键字(C99及更高版本)可以告诉编译器它们不重叠,以便进行这种(非常有价值的)优化。