海湾合作委员会似乎错过了简单的优化

问题描述 投票:6回答:1

我试图用三元运算符的语义引入泛型函数:E1 ? E2 : E3。我看到编译器能够根据三元运算符的E2条件消除E3E1之一的计算。然而,在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

https://godbolt.org/z/HfpNzo

c++ gcc assembly optimization
1个回答
7
投票

我看到,对于三元运算符,编译器能够根据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它们(如果没有写过全局变量),但是不能完全从基本块中进行优化?或许当前的优化器无法利用。就像我说的,看起来像一个错过选择的错误。

© www.soinside.com 2019 - 2024. All rights reserved.