避免使用内联汇编优化掉变量

4
我正在阅读在进行基准测试时防止编译器优化,其中描述了Chandler Carruth的演讲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
}

https://godbolt.org/g/Ndd56K


我认为,因为g将值作为const ref传递给doNotOptimize,编译器可以假设x或更好的是val不会改变,因此它可以优化调用。另一方面,f将一个const指针传递给doNotOptimize,所以x确实可能会改变。 - t.niese
3个回答

9

我应该如何生成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是一个常量,在此调用被内联时,优化器会将其保留为一个常量。您的“memory”破坏说明符没有任何作用,因为这里没有对内存进行修改。

那么我们该怎么办呢?好吧,我们可以通过使用m约束条件来强制分配内存给变量x,即使它不需要,:

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约束将起作用,或者x86特定约束,允许您指定特定的寄存器。这会强制优化器分配一个寄存器来保存该值,即使它不需要:
g():
    mov     eax, 1
    ret

然而,我看不出这两者的意义所在。

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


我曾模糊地认为doNotOptimize()函数应该能够处理指针和非指针类型的输入参数。至于asm约束的含义,我并没有理解。顺便说一句,我注意到Facebook的folly库确实有不同类型的模板特化。我使用了他们的doNotOptimizeAway重写了我的示例:https://godbolt.org/g/C8OvGm - Daniel Näslund
有时候你想要在循环中使用 "+r"(var),来告诉编译器重新计算某些东西。 - Peter Cordes

0
我建议声明。
volatile char x = 1;

但是请注意,编译器“正确地”进行了优化,就像你观察到的那样。


0

对于g(),不会生成任何代码,因为"g"约束允许输入被优化为常量。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接