如何防止一段无副作用的代码被优化掉?

How to prevent a segment of Side Effect-Free code from being optimized away?

提问人:lei hu 提问时间:9/15/2023 最后编辑:Peter Cordeslei hu 更新时间:9/15/2023 访问量:79

问:

考虑一个场景,我构造了一个表示大整数的类 T。此类 T 具有加法运算符函数。

class T {
public:
    T operator+(const T &other) const
}

为了测试它的性能,我想反复 执行此函数足够次数,记录经过的总时间,然后计算平均时间。伪代码如下:

T a, b;
Record the starting timestamp.
for (int i = 0; i < 100000; i++) {
     a + b;
}
Record the ending timestamp.
Calculate the average time.

但是,由于 a+b 运算的结果没有被保存,并且 operator+ 函数也没有以任何方式修改 a 或 b,所以 a + b;是无副作用的代码,将由编译器进行优化。一个可行的解决方案是改变

 a + b;

T t = a + b;

store t somewhere 

但是,这会引入对复制构造函数的调用,并增加存储 t 的开销,从而降低计时测量的准确性。有没有办法告诉编译器不要优化 a+b;?

C++ C++14 编译器优化 微基准测试

评论

0赞 lei hu 9/15/2023
我只想防止 a + b 行;在保留其他优化的同时进行优化,因为我想在启用优化的情况下测量 operator+ 函数可实现的性能。
0赞 Pepijn Kramer 9/15/2023
使用 nanobench 头文件库进行基准测试及其 doNotOptimizeAway
0赞 Toby Speight 9/15/2023
在这种情况下,似乎禁用 LTO 就足够了,假设运算符是在与调用它的代码不同的 TU 中编译的。然后,您的编译器将不知道它是没有副作用的。

答:

1赞 Peter Cordes 9/15/2023 #1

创建一个需要结果的副作用,例如 GNU C 无法优化,并且要求该值存在于寄存器中。但编译为零 asm 指令。asm volatile("" :: "r"(value))volatile

OTOH,这仍然允许将计算提升到循环之外,因此您还需要让编译器忘记每次迭代的值,例如,告诉编译器两个变量都是 asm 语句修改的读/写操作数。这可能与 不兼容。也许对于只适合内存的 BigInt,您可以获取指向局部变量的指针,并使用 clobber 使编译器假设数据已被修改,即使您只有引用。a+babasm("" : "+g"(a), "+g"(b))const"memory"const

您必须检查 asm 以确保没有引入额外的存储/重新加载。(如何消除 GCC/clang 程序集输出中的“噪音”? clobber 不会影响未获取地址的本地 var,但将地址作为输入操作数传递给语句应该使其尊重它们。"memory"asm

各种实现都以这种方式使用 GNU C 内联 asm。与 nanobench 的 doNotOptimizeAway 和 Google::Benchmark 一样,请参阅相关的 Q&As:(Google Benchmark Frameworks DoNotOptimize / 和另一个 re:其中的“内存”clobber)。ISO C++ 没有可移植的方法来执行此操作,因此例如,使用 MSVC,它们可能会将值存储到虚拟变量。DoNotOptimize#ifdef __GNUC__volatile int sink

但是,在循环携带的依赖链中引入存储/重新加载可能会像调试构建一样速度减慢(不像只是重复存储 L1d 缓存可以吸收的相同位置),因此约束没有很好的可移植等价物来使编译器假设值已被修改而没有实际运行任何指令。"+g"(var)

"g"允许编译器选择内存或寄存器。但是 clang 可能总是更喜欢内存,所以可以解决这个问题。"+r,m"

-1赞 SalahSoliman 9/15/2023 #2

我能想到的最简单的方法是通过编译器标志将优化级别设置为零-O0

这是一个使用 Godbolt 的简单演示

设置为零时,需要 10180 个时间单位。-O0

设置为 3 时,只需 90 个时间单位。-O3

您还可以检查生成的汇编代码。

评论

2赞 Peter Cordes 9/15/2023
禁用优化的基准测试毫无意义,与优化的代码存在不同的瓶颈(通常是存储转发延迟,或者在 C++ 中,对微小函数的非内联调用),因此您尝试测量的任何小规模局部效应甚至都不再存在。绩效评估的惯用方式?/ 为什么 clang 在 -O0 下会产生低效的 asm(对于这个简单的浮点和)?/ 如何优化这些循环(禁用编译器优化)?
0赞 Community 9/18/2023
您的答案可以通过额外的支持信息得到改进。请编辑以添加更多详细信息,例如引文或文档,以便其他人可以确认您的答案是正确的。您可以在帮助中心找到有关如何写出好答案的更多信息。