Rac*_*box 6 c++ gcc micro-optimization compiler-optimization
我试图了解是否删除局部中间变量可以导致更好的优化代码。考虑以下MWE,要特别注意这两个函数f和g:
struct A {
double d;
};
struct B {
double s;
};
struct C {
A a;
B b;
};
A geta();
B getb();
C f() {
const A a = geta();
const B b = getb();
C c;
c.a = a;
c.b = b;
return c;
}
C g() {
C c;
c.a = geta();
c.b = getb();
return c;
}
Run Code Online (Sandbox Code Playgroud)
既f与g呼叫geta()和getb()来填充类的实例C,然后将其返回,但f使用两个本地中间变量来存储的返回值geta()和getb(),而g直接返回值分配给成员c。
与编译gcc -O3,9.2版本,这两个功能的二进制文件f,并g是完全一样的。但是,向A或B类中添加另一个变量会导致不同的二进制文件。特别是,二进制文件for f具有更多指令。带-O3标志的clang v8.0.0的情况相同。
这是怎么回事 为什么编译器无法优化fwhen 的局部中间变量A或B变得更复杂?是不是f和g等价的代码?
此外,该行为是不是MSVC v19.22用相同的/O2标志:从微软编译器在第一种情况下,即与两个班已经有不同的二进制文件A,并B通过一个单一的组成double。
我正在使用Godbolt:您可以在此处找到产生不同二进制代码的代码。
这是一个错过的优化
这两个函数都没有获取 的地址C c,因此转义分析应该很容易证明它是一个纯本地函数,没有其他函数可以有指针指向。 geta()并且getb()无法直接读取或写入该变量,因此将geta()返回值直接存储到c.a堆栈中而不是临时存储在堆栈中是安全的。
令人惊讶的是,GCC、clang、ICC 和 MSVC 都错过了这种优化,大多数使用调用保留寄存器来保存返回geta()值,直到 之后getb()。 https://godbolt.org/z/WQ9MAF 至少对于 x86-64;我基本上没有检查其他 ISA 或较旧的编译器版本。
有趣的事实:clang 3.5 即使对于 也有这种错过的优化g(),破坏了源代码提高效率的尝试。
有趣的事实#2:在 GCC9.2 中,编译为 C 而不是 C++ 会使 GCC 的工作变得更糟糕,从而导致g(). (我必须更改为typedef struct Atag {...} A;但编译它,因为 C++ 仍然优化g()。https ://godbolt.org/z/_Y95nj)
clang8.0 产生高效的g()有/无-xc. 无论哪种方式,ICC 都会产生低效g()。
ICC的f()比它的还要糟糕g()。
MSVC 的g()效率正如您所希望的那样;Windows x64 调用约定通过隐藏指针返回结构,MSVC 从未优化它以将指针传递给其自己的返回值对象。(如果它自己的调用者也可能进行此类优化,那么它可能无法证明它是安全的。)
显然,如果geta()和getb()可以内联,这就消除了优化器的任何疑问,并且它应该更容易/可靠地进行优化。