GCC похоже пропускает простую оптимизацию - PullRequest
0 голосов
/ 13 сентября 2018

Я пытаюсь ввести обобщенную функцию с семантикой тернарного оператора: E1 ? E2 : E3.Я вижу, что компилятор может исключить вычисление одного из E2 или E3 в зависимости от условия E1 для троичного оператора.Однако GCC пропускает эту оптимизацию в случае вызова функции ternary (даже когда E2 / E3 не имеет побочных эффектов).

В приведенном ниже листинге функция ternary написана так, чтобы вести себя аналогичнотроичный оператор.Однако GCC испускает потенциально тяжелый вызов функции f, который, по-видимому, может быть исключен для некоторых входных значений (именно так, как это делается для троичного оператора), потому что f объявлен с чистым атрибутом - пожалуйста, посмотрите на ссылку godbolt для сгенерированного кода сборкиGCC.

Это что-то, что можно улучшить в 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

1 Ответ

0 голосов
/ 13 сентября 2018

Я вижу, что компилятор может исключить вычисление одного из E2 или E3 в зависимости от условия E1 (если у 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().


Здесь есть пропущенная оптимизация, о которой вы должны сообщить в bugzilla GCC (используйте ключевое слово 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)) не может даже читать глобальные переменные, только ее аргументы. (Таким образом, без аргументов, она всегда должна возвращать константу.)

HVD указывает, что gcc не требует никаких затратинформация для f(). Даже если бы мог оптимизировать вызов до ((pure)) f(), а также до ((const)) f, возможно, этого не произошло, потому что он не знал, что это дороже, чемусловная ветвь? Возможно, компиляция с оптимизацией по профилю убедит gcc что-то сделать?

Но, учитывая, что он сделал условный вызов ((const)) f в foo2, gcc может просто не знать, что он может оптимизироватьвызовы ((pure)) функций? Может быть, он может только CSE их (если глобальные не были написаны), но не оптимизировать полностью от базового блока? Или, может быть, текущий оптимизатор просто не в состоянии воспользоваться. Как я уже сказал, выглядит какмиошибка ssed-opt.

...