避免使用内联asm优化远离变量

时间:2017-06-15 08:45:46

标签: c++ assembly performance-testing compiler-optimization inline-assembly

我正在阅读Preventing compiler optimizations while benchmarking,其中介绍了Chandler Carruths谈话CppCon 2015: Chandler Carruth "Tuning C++: Benchmarks, and CPUs, and Compilers! Oh My!"中的clobber()escape()如何影响编译器。

通过阅读,我假设如果我有一个像“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

3 个答案:

答案 0 :(得分:6)

为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");
}

g参数使用val约束,该参数表示它可以存储在 通用寄存器,内存或作为常​​量,无论优化器哪个最方便。由于val是常量,因此在内联此调用时,优化程序将其保留为常量。你的“内存”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,这也很有效。 (当然,你不能使用模板功能。好吧,除非你想要abuse C++11's extern templates。)

答案 1 :(得分:1)

我建议宣布

volatile char x = 1;

但请注意编译器是&#34;对&#34;像观察一样优化。

答案 2 :(得分:0)

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