assembly GCC似乎错过了简单的优化

irlmq6kh  于 2023-10-19  发布在  其他
关注(0)|答案(1)|浏览(75)

我试图引入一个具有三元运算符语义的泛型函数:E1 ? E2 : E3。我看到编译器能够消除E2E3之一的计算,这取决于三元运算符的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

https://godbolt.org/z/HfpNzo

ma8fv8wu

ma8fv8wu1#

我看到编译器能够根据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没有。
**要让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函数调用,而不仅仅是将重复调用压缩为一个。

//__attribute__((const))  // truly pure function of its args, can't even read globals

__attribute__((pure))  // no side effects that the rest of the program cares about.
int foo(int);

int bar(int a) {
    foo(a);
    return a;
}

Godbolt- GCC和clang都只编译到mov eax, edi以返回第一个arg。他们甚至警告函数调用是无用的,因为没有使用返回值:

# GCC
<source>:6:8: warning: statement has no effect [-Wunused-value]
     foo(a);
     ~~~^~~

# clang
<source>:6:5: warning: ignoring return value of function declared with pure attribute [-Wunused-value]
    foo(a);
    ^~~ ~

相关问题