避免使用内联 asm 优化 away 变量

Avoid optimizing away variable with inline asm

我正在阅读Preventing compiler optimizations while benchmarking that describes how clobber() and escape() from Chandler Carruths talk CppCon 2015: Chandler Carruth "Tuning C++: Benchmarks, and CPUs, and Compilers! Oh My!"影响编译器。

通过阅读,我假设如果我有一个像 "g"(val) 这样的输入约束,那么编译器将无法优化掉 val。但是在下面的g()中,没有生成代码。为什么?

如何重写 doNotOptimize() 以确保为 g() 生成代码?

template <typename T>
void doNotOptimize(T const& val) {
  asm volatile("" : : "g"(val) : "memory");
}

void f() {
  char x = 1;
  doNotOptimize(&x);    // x is NOT optimized away
}

void g() {
  char x = 1;
  doNotOptimize(x);     // x is optimized away
}

https://godbolt.org/g/Ndd56K

为 g() 生成代码究竟意味着什么?如果你自己写,你会写什么代码?说真的,这是一个真正的问题。在开始从编译器中哄骗它之前,您必须决定您期望的输出。

不管怎样,让我们​​看看你现在有什么。在 f() 中,

void f() {
  char x = 1;
  doNotOptimize(&x);    // x is NOT optimized away
}

您正在使用 x 地址 ,这会阻止优化器在寄存器中分配它。它必须在内存中分配才能拥有地址。

然而,在 g() 中,

void g() {
  char x = 1;
  doNotOptimize(x);     // x is optimized away
}

x 只是一个局部变量,任何理智的优化器都会在寄存器中分配它,或者在这种情况下作为常量分配。这是允许的,因为您永远不会使用它的地址;你只是使用它的价值。因此,例如,编译器可能会生成如下代码:

g():
    mov  al, 1      // store 1 in BYTE-sized register AL
    ...

或者在这种情况下根本不生成任何代码,并用变量的常量值替换对变量的任何使用。

您的 doNotOptimize 代码,

template <typename T>
void doNotOptimize(T const& val) {
  asm volatile("" : : "g"(val) : "memory");
}

val参数使用g约束,表示它可以存储在或者一个通用寄存器,内存 作为常量,以优化器认为最方便的为准。由于 val 是常量,因此当此调用被内联时,优化器将其保留为常量。你的 "memory" clobber 说明符没有效果,因为这里没有修改内存。

那我们能做什么呢?好吧,我们可以强制变量 x 在内存中分配,即使它不需要,通过使用 m 约束:

template <typename T>
void doNotOptimize(T const& val) {
  asm volatile("" : : "m"(val) : "memory");
}

void g() {
  char x = 1;
  doNotOptimize(x);
}

现在编译器无法优化 x 的存储并被迫发出以下代码:

g():
    mov  BYTE PTR [rsp-1], 1
    ret

请注意,这与声明 x 变量 volatile 的效果基本相同。

还记得我一开始问的问​​题吗?这是你想要的输出吗?

或者,您可能希望编译器发出立即注册移动。如果是这样,r 约束将起作用——或者 any of the x86-specific constraints 允许您指示特定的寄存器。这会强制优化器在寄存器中分配值,即使它不需要是:

g():
    mov     eax, 1
    ret

但是,我看不出其中任何一个有什么意义。

如果您想制作一个微基准测试来测试使用单个 const-reference 参数调用函数的开销,那么更好的选择是确保被调用函数的定义对优化器不可见.然后,它无法内联该函数并且 必须 安排调用,包括所有必要的设置。如果你只是 studying how a compiler might emit that code. (Naturally, you can't use a template function, though. Well, unless you wanted to abuse C++11's extern templates,这也很有效。)

我建议申报

volatile char x = 1;

但请注意,编译器"right" 会像您观察到的那样进行优化。

没有为 g() 生成代码,因为 "g" 约束允许将输入优化为常量。