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

Dan*_*und 3 c++ assembly inline-assembly performance-testing compiler-optimization

我正在阅读预防编译器优化,同时描述Chandler Carruths 如何clobber()escape()来自 CppCon 2015:Chandler Carruth"调优C++:基准测试,CPU和编译器!哦我的!".影响编译器.

从阅读那里,我假设如果我有一个输入约束,如"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
}
Run Code Online (Sandbox Code Playgroud)

https://godbolt.org/g/Ndd56K

Cod*_*ray 6

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

无论如何,让我们来看看你现在拥有的东西.在f()中,

void f() {
  char x = 1;
  doNotOptimize(&x);    // x is NOT optimized away
}
Run Code Online (Sandbox Code Playgroud)

你正在服用的地址x,这阻止优化在寄存器分配它.它必须在内存中分配才能拥有一个地址.

但是,在g()中,

void g() {
  char x = 1;
  doNotOptimize(x);     // x is optimized away
}
Run Code Online (Sandbox Code Playgroud)

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

g():
    mov  al, 1      // store 1 in BYTE-sized register AL
    ...
Run Code Online (Sandbox Code Playgroud)

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

你的doNotOptimize代码,

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

使用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);
}
Run Code Online (Sandbox Code Playgroud)

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

g():
    mov  BYTE PTR [rsp-1], 1
    ret
Run Code Online (Sandbox Code Playgroud)

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

还记得我刚开始提出的问题吗?那是你想要的输出吗?

或者,您可能希望编译器发出即时寄存器移动.如果是这样,r约束将起作用 - 或任何允许您指定特定寄存器的x86特定约束.这会强制优化器在寄存器中分配值,即使它不需要:

g():
    mov     eax, 1
    ret
Run Code Online (Sandbox Code Playgroud)

但是,我不能看出其中任何一个的意义.

如果你想制作一个测试使用单个const-reference参数调用函数的开销的微基准测试,那么更好的选择是确保调用的函数的定义对于优化器是不可见的.然后,它无法内联该功能并且必须安排进行呼叫,包括所有必要的设置.如果您只是研究编译器如何发出该代码,这也很有效.(当然,你不能使用模板函数.好吧,除非你想滥用C++ 11的extern模板.)