我试图用三元运算符的语义引入泛型函数:E1 ? E2 : E3
。我看到编译器能够根据三元运算符的E2
条件消除E3
或E1
之一的计算。然而,在ternary
函数调用的情况下,GCC错过了这种优化(即使E2
/ E3
没有副作用)。
在下面的列表中,函数ternary
被写成与三元运算符类似。然而,GCC发出了对函数f
的潜在重要调用,这似乎可以消除某些输入值(确切地说它是如何为三元运算符完成的)因为f
被声明为纯属性 - 请查看GCC生成的汇编代码的godbolt链接。
它是否可以在GCC(优化空间)中得到改进,或者C ++标准是否明确禁止这种优化?
// Very heavy function
int f() __attribute__ ((pure));
inline int ternary(bool cond, int n1, int n2) {
return cond ? n1 : n2;
}
int foo1(int i) {
return i == 0 ? f() : 0;
}
int foo2(int i) {
return ternary(i == 0, f(), 0);
}
与-O3 -std=c++11
的装配清单:
foo1(int):
test edi, edi
jne .L2
jmp f()
.L2:
xor eax, eax
ret
foo2(int):
push rbx
mov ebx, edi
call f()
test ebx, ebx
mov edx, 0
pop rbx
cmovne eax, edx
ret
我看到,对于三元运算符,编译器能够根据E1条件(只要E2 / E3没有副作用)消除E2或E3中的一个的计算。
编译器不会消除它;它首先从来没有优化成cmov
。 C ++抽象机器不评估三元运算符的未使用侧。
int a, b;
void foo(int sel) {
sel ? a++ : b++;
}
像这样汇编(Godbolt):
foo(int):
test edi, edi
je .L2 # if(sel==0) goto
add DWORD PTR a[rip], 1 # ++a
ret
.L2:
add DWORD PTR b[rip], 1 # ++b
ret
如果两个输入都没有任何副作用,则三元运算符只能优化为asm cmov
。否则他们就不完全等同。
在C ++抽象机器(即gcc优化器的输入)中,你的foo2
总是调用f()
,而你的foo1
则不然。 foo1编译它的方式就不足为奇了。
为了让foo2以这种方式编译,它必须优化掉对f()
的调用。它始终被称为为ternary()
创建一个arg。
这里有一个错过优化,您应该报告GCC的bugzilla(使用missed-optimization
关键字作为标记)。 https://gcc.gnu.org/bugzilla/enter_bug.cgi?product=gcc
应该能够优化对int f() __attribute__ ((pure));
的调用。它可以读取全局变量,但不能有任何副作用。 (https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html)
正如@melpomene在评论中发现的那样,int f() __attribute__ ((const));
确实为您提供了所需的优化。 __attribute__((const))
函数甚至不能读取全局变量,只能读取它的参数。 (因此没有args,它必须始终返回一个常量。)
HVD指出gcc没有f()
的任何费用信息。即使它可以优化掉对((pure)) f()
以及((const)) f
的调用,也许它没有,因为它不知道它比条件分支更昂贵?可能使用配置文件引导优化进行编译会说服gcc做些什么?
但鉴于它在((const)) f
中调用foo2
有条件,gcc可能只是不知道它可以优化对((pure))
函数的调用?也许它只能CSE它们(如果没有写过全局变量),但是不能完全从基本块中进行优化?或许当前的优化器无法利用。就像我说的,看起来像一个错过选择的错误。