我试图引入一个具有三元运算符语义的泛型函数:E1 ? E2 : E3
。我看到编译器能够消除E2
或E3
之一的计算,这取决于三元运算符的E1
条件。然而,GCC在ternary
函数调用的情况下错过了这种优化(即使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
1条答案
按热度按时间ma8fv8wu1#
我看到编译器能够根据E1条件(只要E2/E3没有副作用)为三元运算符消除E2或E3之一的计算。
编译器不会消除它;它只是从来没有把它优化成一个
cmov
摆在首位。C++抽象机 * 不 * 计算三元运算符的未使用端。像这样编译(Godbolt):
如果两个输入都没有任何副作用,则三元运算符只能优化为asm
cmov
。否则它们并不完全相等。**在C++抽象机器中(即gcc优化器的输入),
foo2
总是调用f()
,而foo1
没有。**要让foo 2以这种方式编译,它必须优化对
f()
的调用。**它总是被调用来为ternary()
创建一个arg。这里有一个遗漏的优化,您应该在GCC的bugzilla上报告(使用
missed-optimization
关键字作为标记)。https://gcc.gnu.org/bugzilla/enter_bug.cgi?product=gcc对
__attribute__ ((pure)) int f();
的调用 * 应该 * 能够被优化1。它可以 * 读取 * 全局变量,但不能有任何副作用影响程序中的其他内容。(https://gcc.gnu.org/onlinedocs/gcc/Common-Function-Attributes.html)正如@melpomene在评论中发现的那样,
__attribute__ ((const)) int f();
确实给了你给予你想要的优化。一个__attribute__((const))
函数甚至不能读取全局变量,只能读取它的args。(因此,如果没有args,它必须始终返回一个常量。HVD指出,gcc没有任何关于
f()
的成本信息。即使它 * 可以 * 优化掉对((pure)) f()
和((const)) f
的调用,也许它没有优化,因为它不知道它比条件分支更昂贵?也许用配置文件引导的优化来编译会说服gcc做些什么?但是考虑到它在
foo2
中对((const)) f
的调用是有条件的,gcc可能不知道它可以优化对((pure))
函数的调用。也许它只能CSE它们(如果没有全局变量被写入),但不能完全从基本块中优化出来?或者,也许当前的优化器只是未能利用。就像我说的,看起来像一个missed-opt bug。脚注1:GCC和clang将完全优化
pure
函数调用,而不仅仅是将重复调用压缩为一个。Godbolt- GCC和clang都只编译到
mov eax, edi
以返回第一个arg。他们甚至警告函数调用是无用的,因为没有使用返回值: