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

问题描述 投票:0回答:1

考虑一个场景,我构造了一个代表大整数的类 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 benchmarking compiler-optimization microbenchmark
1个回答
0
投票

创建需要结果的副作用,例如GNU C

asm volatile("" :: "r"(value))
volatile
,因此无法优化掉,并且要求该值存在于寄存器中。但编译为零 asm 指令。

OTOH,这仍然允许将

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

您必须检查汇编以确保没有引入额外的存储/重新加载。 (如何从 GCC/clang 汇编输出中消除“噪音”?)。

"memory"
破坏者不会影响地址尚未被获取的本地变量,但是将地址作为输入操作数传递给
asm
语句应该使其尊重它们。

各种

DoNotOptimize
实现都以这种方式使用 GNU C 内联汇编
#ifdef __GNUC__
。像 nanobench 的
doNotOptimizeAway
和 Google::Benchmark 一样,请参阅相关问答:(Google Benchmark Frameworks DoNotOptimize / 以及另一个回复:其中的
"memory"
clobber
)。 ISO C++ 没有可移植的方法来做到这一点,因此例如使用 MSVC,它们可能会将值存储到
volatile int sink
虚拟变量。

但是在循环携带的依赖链中引入存储/重新加载可能会像调试构建一样造成巨大的减慢(与 L1d 缓存可以吸收的重复存储相同位置不同),因此对于

"+g"(var)
约束没有良好的可移植等效项让编译器假设该值已被修改,而不实际运行任何指令。

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

© www.soinside.com 2019 - 2024. All rights reserved.