我有些困惑于 Google Benchmark Framework 的函数 void DoNotOptimize
的实现方式 (这里定义了该函数):
template <class Tp>
inline BENCHMARK_ALWAYS_INLINE void DoNotOptimize(Tp const& value) {
asm volatile("" : : "r,m"(value) : "memory");
}
template <class Tp>
inline BENCHMARK_ALWAYS_INLINE void DoNotOptimize(Tp& value) {
#if defined(__clang__)
asm volatile("" : "+r,m"(value) : : "memory");
#else
asm volatile("" : "+m,r"(value) : : "memory");
#endif
}
这段代码将变量实例化,如果不是常量,则告诉编译器忘记其先前的任何值。("+r"
是一个RMW操作数)。
并且始终使用"memory"
clobber,这是一个编译器屏障,用于防止重新排序加载/存储,即确保所有全局可访问对象的内存与C ++抽象机器同步,并假定它们也可能已被修改。
我远非低级代码方面的专家,但据我所知,该函数作为读/写屏障。因此 - 基本上 - 它确保传递的值位于寄存器或内存中。
虽然这似乎完全合理,如果我想保留函数的结果(应该进行基准测试),但我对编译器留下的自由度感到有些惊讶。
我对给定代码的理解是,每当调用DoNotOptimize
时,编译器都可以插入实例化点,这意味着在重复执行(例如,在循环中)时会产生显着的开销。
当值不应该优化时仅为单个标量值时,如果编译器确保该值驻留在寄存器中,似乎就足够了。
难道不应该区分指针和非指针吗?
template< class T >
inline __attribute__((always_inline))
void do_not_optimize( T&& value ) noexcept {
if constexpr( std::is_pointer_v< T > ) {
asm volatile("":"+m"(value)::"memory");
} else {
asm volatile("":"+r"(value)::);
}
}