避免使用内联 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
}
为 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"
约束允许将输入优化为常量。
我正在阅读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
}
为 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"
约束允许将输入优化为常量。