Я сравнивал вывод 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()
имеют тот же самый код, что и выше, и что компилятор фактически вызывает их.