钱德勒·卡鲁斯(Chandler Carruth)在CppCon2015 talk中引入了两个函数,可用于对优化程序进行一些细粒度的抑制。它们对于编写微基准很有用,这些微基准使优化程序不会简单地陷入毫无意义的境地。
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
中的Assembly语句并未声明任何特定内容对于程序集都是可见的。)所有这些都是在没有这些“障碍”直接生成任何其他代码的情况下发生的。它们纯粹是编译时工件。
尽管它们使用了GCC和Clang支持的语言扩展。使用MSVC时是否有办法具有类似的行为?
†要了解为什么优化器必须采用这种方式,请想象一下汇编块是否是一个向内存中的每个字节加1的循环。
最佳答案
给定your approximation of 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的那一刻,该策略就失败了。你有什么想法?答案:我们可以利用数论中难以证明的特性,例如Fermat's Last Theorem:
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;
}