MSVC 中微基准测试的优化屏障:告诉优化器您的内存
Optimization barrier for microbenchmarks in MSVC: tell the optimizer you clobber memory?
Chandler Carruth在他的CppCon2015演讲中介绍了两个函数,可用于对优化器进行一些细粒度的抑制。它们对于编写微基准测试很有用,优化器不会简单地陷入毫无意义。
void clobber() {
asm volatile("" : : : "memory");
}
void escape(void* p) {
asm volatile("" : : "g"(p) : "memory");
}
它们使用内联程序集语句来更改优化程序的假设。
clobber
中的程序集语句声明其中的程序集代码可以在内存中的任何位置读取和写入。实际的汇编代码为空,但优化器不会查看它,因为它asm volatile
。当我们告诉它代码可能会在内存中的任何地方读写时,它就会相信它。这有效地防止优化程序在调用clobber
之前重新排序或丢弃内存写入,并强制在调用clobber
†后读取内存。
escape
中的那个 ,另外使指针p
对程序集块可见。同样,由于优化器不会查看实际的内联汇编代码,因此代码可以为空,并且优化器仍将假定块使用指针p
所指向的地址。这有效地强制任何p
指向内存中,而不是不在寄存器中,因为程序集块可能会从该地址执行读取。
(这很重要,因为 clobber
函数不会强制读取或写入编译器决定放入寄存器中的任何内容,因为 clobber
中的汇编语句没有声明任何特定内容必须对程序集可见。
所有这些都是在没有这些"障碍"直接生成任何额外代码的情况下发生的。 它们纯粹是编译时工件。
不过,它们使用GCC和Clang支持的语言扩展。有没有办法在使用 MSVC 时具有类似的行为?
† 要理解为什么优化器必须这样思考,想象一下,如果程序集块是一个循环,将 1 添加到内存中的每个字节。
鉴于您对 escape()
的近似值,您也应该可以接受以下近似clobber()
(请注意,这是一个草案想法,将一些解决方案推迟到函数nextLocationToClobber()
的实现):
// always returns false, but in an undeducible way
bool isClobberingEnabled();
// The challenge is to implement this function in a way,
// that will make even the smartest optimizer believe that
// it can deliver a valid pointer pointing anywhere in the heap,
// stack or the static memory.
volatile char* nextLocationToClobber();
const bool clobberingIsEnabled = isClobberingEnabled();
volatile char* clobberingPtr;
inline void clobber() {
if ( clobberingIsEnabled ) {
// This will never be executed, but the compiler
// cannot know about it.
clobberingPtr = nextLocationToClobber();
*clobberingPtr = *clobberingPtr;
}
}
更新
问题:您如何确保isClobberingEnabled
"以不可推断的方式"返回false
?当然,将定义放在另一个翻译单元中是微不足道的,但是一旦启用LTCG,该策略就失败了。你有什么想法?
答:我们可以利用数论中一个难以证明的性质,例如费马大定理:
bool undeducible_false() {
// It took mathematicians more than 3 centuries to prove Fermat's
// last theorem in its most general form. Hardly that knowledge
// has been put into compilers (or the compiler will try hard
// enough to check all one million possible combinations below).
// Caveat: avoid integer overflow (Fermat's theorem
// doesn't hold for modulo arithmetic)
std::uint32_t a = std::clock() % 100 + 1;
std::uint32_t b = std::rand() % 100 + 1;
std::uint32_t c = reinterpret_cast<std::uintptr_t>(&a) % 100 + 1;
return a*a*a + b*b*b == c*c*c;
}
我用以下内容代替了escape
。
#ifdef _MSC_VER
#pragma optimize("", off)
template <typename T>
inline void escape(T* p) {
*reinterpret_cast<char volatile*>(p) =
*reinterpret_cast<char const volatile*>(p); // thanks, @milleniumbug
}
#pragma optimize("", on)
#endif
它并不完美,但我认为它已经足够接近了。
可悲的是,我没有办法模仿clobber
.
- 对于堆上的页面对齐内存分配是否有任何优化或不同的 API?
- C++二和.优化内存使用
- 如何控制或优化或删除或释放 UNION 中未使用的内存
- 如果 RMW 操作没有任何变化,是否可以针对所有内存顺序对其进行优化
- std::stable_sort: 如何选择内存优化算法而不是时间优化算法?
- 字符串编码用于内存优化
- 编译器内存优化 - 重用现有块
- 编译器是否优化析构函数中的内存集
- 矢量函数的C 内存优化
- 如何在CPU和内存中优化C 中的重型地图插入
- C++对间接运算符的标准描述是否保证内存写入不会被优化掉
- 内存分配,用于在C 11中循环中函数的返回值:如何优化
- 编译器优化了内存分配
- C++字符串内存重用优化
- 优化地形渲染的内存
- 内存对齐优化不仅性能,而且内存大小
- 优化数据结构,使其充分利用虚拟内存
- 有没有一种更快的方法或优化我可以应用到我的即兴内存池
- 优化内存读取和写入的长期运行
- 优化内存和性能的传输数据缓冲区