Почему GCC не оптимизирует a * a * a * a * a * a до (a * a * a) * (a * a * a)? - PullRequest
2060 голосов
/ 21 июня 2011

Я занимаюсь численной оптимизацией научного приложения.Одна вещь, которую я заметил, заключается в том, что GCC оптимизирует вызов pow(a,2), скомпилировав его в a*a, но вызов pow(a,6) не оптимизируется и фактически вызовет библиотечную функцию pow, что значительно снижает производительность.(Напротив, Компилятор Intel C ++ , исполняемый файл icc, устранит библиотечный вызов для pow(a,6).)

Что мне интересно, так это то, что когда я заменил pow(a,6) наa*a*a*a*a*a с использованием GCC 4.5.1 и опций "-O3 -lm -funroll-loops -msse4", он использует 5 mulsd инструкций:

movapd  %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm14, %xmm13

, а если я напишу (a*a*a)*(a*a*a), он выдаст

movapd  %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm14, %xmm13
mulsd   %xmm13, %xmm13

, который уменьшает количество команд умножения до 3. icc ведет себя аналогично.

Почему компиляторы не распознают этот прием оптимизации?

Ответы [ 12 ]

21 голосов
/ 01 октября 2013

На этот вопрос уже есть несколько хороших ответов, но для полноты картины я хотел бы указать, что применимый раздел стандарта C - это 5.1.2.2.3 / 15 (который совпадает с разделом 1.9 /9 в стандарте C ++ 11).В этом разделе говорится, что операторы могут быть перегруппированы, только если они действительно ассоциативны или коммутативны.

11 голосов
/ 16 июня 2016

gcc на самом деле может сделать эту оптимизацию, даже для чисел с плавающей точкой.Например,

double foo(double a) {
  return a*a*a*a*a*a;
}

становится

foo(double):
    mulsd   %xmm0, %xmm0
    movapd  %xmm0, %xmm1
    mulsd   %xmm0, %xmm1
    mulsd   %xmm1, %xmm0
    ret

с -O -funsafe-math-optimizations.Это переупорядочение нарушает IEEE-754, поэтому требует наличия флага.

Знаковые целые числа, как отметил в комментарии Питер Кордес, могут выполнить эту оптимизацию без -funsafe-math-optimizations, поскольку она выполняется именно тогда, когда переполнения нети если есть переполнение, вы получаете неопределенное поведение.Таким образом, вы получаете

foo(long):
    movq    %rdi, %rax
    imulq   %rdi, %rax
    imulq   %rdi, %rax
    imulq   %rax, %rax
    ret

с -O.Для целых чисел без знака это даже проще, поскольку они работают с модами степеней 2 и поэтому могут свободно переупорядочиваться даже при переполнении.

...