Суровые различия в сборке сборок с плавающей запятой <и> =
Я экспериментирую с созданной сборкой и нашел интересную вещь.
Есть две функции, выполняющие одинаковые вычисления. Единственное различие между ними - это способ суммирования результатов.
#include <cmath>
double func1(double x, double y)
{
double result1;
double result2;
if (x*x < 0.0) result1 = 0.0;
else
{
result1 = x*x+x+y;
}
if (y*y < 0.0) result2 = 0.0;
else
{
result2 = y*y+y+x;
}
return (result1 + result2) * 40.0;
}
double func2(double x, double y)
{
double result = 0.0;
if (x*x >= 0.0)
{
result += x*x+x+y;
}
if (y*y >= 0.0)
{
result += y*y+y+x;
}
return result * 40.0;
}
Сборка, сгенерированная с помощью x86 clang 3.7 с -O2
на gcc.godbolt.org, пока еще очень отличается и неожиданна. (компиляция на gcc приводит к аналогичной сборке)
.LCPI0_0:
.quad 4630826316843712512 # double 40
func1(double, double): # @func1(double, double)
movapd %xmm0, %xmm2
mulsd %xmm2, %xmm2
addsd %xmm0, %xmm2
addsd %xmm1, %xmm2
movapd %xmm1, %xmm3
mulsd %xmm3, %xmm3
addsd %xmm1, %xmm3
addsd %xmm0, %xmm3
addsd %xmm3, %xmm2
mulsd .LCPI0_0(%rip), %xmm2
movapd %xmm2, %xmm0
retq
.LCPI1_0:
.quad 4630826316843712512 # double 40
func2(double, double): # @func2(double, double)
movapd %xmm0, %xmm2
movapd %xmm2, %xmm4
mulsd %xmm4, %xmm4
xorps %xmm3, %xmm3
ucomisd %xmm3, %xmm4
xorpd %xmm0, %xmm0
jb .LBB1_2
addsd %xmm2, %xmm4
addsd %xmm1, %xmm4
xorpd %xmm0, %xmm0
addsd %xmm4, %xmm0
.LBB1_2:
movapd %xmm1, %xmm4
mulsd %xmm4, %xmm4
ucomisd %xmm3, %xmm4
jb .LBB1_4
addsd %xmm1, %xmm4
addsd %xmm2, %xmm4
addsd %xmm4, %xmm0
.LBB1_4:
mulsd .LCPI1_0(%rip), %xmm0
retq
func1
компилируется в ветвящуюся сборку с гораздо меньшими инструкциями, чем func2
. таким образом, func2
ожидается намного медленнее, чем func1
.
Может ли кто-нибудь объяснить это поведение?
Ответы
Ответ 1
Причина такого поведения операторов сравнения <
или >=
отличается от того, является ли ваш double
NaN
или не NaN
. Все сравнения, в которых один из операндов NaN
возвращает false
. Таким образом, ваш x*x < 0.0
всегда будет ложным независимо от того, является ли x
NaN
или нет. Таким образом, компилятор может безопасно оптимизировать это. Однако случай x * x >= 0
будет вести себя по-разному для значений NaN
и non NaN
, поэтому компилятор оставляет условные переходы в сборке.
Это то, что cppreference говорит о сравнении с участием NaN:
значения операндов после преобразования сравниваются в обычном математическом смысле (за исключением того, что положительные и отрицательные нули сравниваются равными, и любое сравнение с использованием значения NaN возвращает ноль)