Почему эта последовательность инструкций быстрее?

Я сравнивал вывод GCC и Clang для оценки выражения с плавающей запятой и наткнулся на разницу в производительности, которую я не мог объяснить.

Исходный код

float evaluate(float a, float b) {
    return (a - b + 1.0f) * (a - b) * (a - b - 1.0f);
}

GCC 7.2 (-std = С++ 1y -O2), полученный

evaluate(float, float):
  subss xmm0, xmm1
  movss xmm2, DWORD PTR .LC0[rip]
  movaps xmm1, xmm0
  addss xmm1, xmm2
  mulss xmm1, xmm0
  subss xmm0, xmm2
  mulss xmm0, xmm1
  ret
.LC0:
  .long 1065353216

В то время как Clang 5.0.0 (-std = С++ 1y -O2) произвел

.LCPI0_0:
  .long 1065353216 # float 1
.LCPI0_1:
  .long 3212836864 # float -1
evaluate(float, float): # @evaluate(float, float)
  subss xmm0, xmm1
  movss xmm1, dword ptr [rip + .LCPI0_0] # xmm1 = mem[0],zero,zero,zero
  addss xmm1, xmm0
  mulss xmm1, xmm0
  addss xmm0, dword ptr [rip + .LCPI0_1]
  mulss xmm0, xmm1
  ret

GCC, по-видимому, предпочитает movaps над movss, хотя в этом случае достаточно movss. Как отмечалось Peter Cordes, это на самом деле лучше, чем использование movaps, чтобы избежать ларьков из частичных обновлений в регистры XMM.

GCC использует три вместо двух регистров XMM.

Clang использует две константы и только addss для них, а не только одну и использует субматы для вычитания одного из регистра.

Я рисую истинные зависимости уровня инструкций для этих последовательностей, а Clang на один уровень короче.

По производительности, совсем не так, как ожидалось, версия GCC примерно на 30% быстрее.

Я тестировал это на процессорах Intel (Broadwell) и AMD (Bulldozer), и я не понимаю, почему здесь был бы быстрее код GCC.

В исходном бенчмарке использовался плохой встроенный код asm. После создания двух объектных файлов (один с GCC, один с Clang) для кода С++ интересующей функции и связывания с ними с GCC разница в производительности исчезла. Питер Кордес намекнул, что это может быть из-за -O1 оставляя директивы .p2align.

Я просмотрел вывод компилятора нового метода бенчмаркинга и утверждал, что обе реализации evaluate() имеют тот же самый код, что и выше, и что компилятор фактически вызывает их.