Почему компилятор генерирует дополнительные sqrts в скомпилированном ассемблере

Я пытаюсь определить время, затрачиваемое на вычисление sqrt, используя следующий простой C-код, где readTSC() является функцией чтения счетчика циклов процессора.

double sum = 0.0;
int i;
tm = readTSC();
for ( i = 0; i < n; i++ )
   sum += sqrt((double) i);
tm = readTSC() - tm;
printf("%lld clocks in total\n",tm);
printf("%15.6e\n",sum);

Однако, когда я распечатал код сборки, используя

gcc -S timing.c -o timing.s

на машине Intel, результат (показанный ниже) был неожиданным?

Почему в ассемблере есть два sqrts с одним, использующим инструкцию sqrtsd, а другой с помощью вызова функции?. Это связано с разворачиванием цикла и попыткой выполнить два sqrts на одной итерации

И как понять строку

ucomisd %xmm0, %xmm0

Почему он сравнивает %xmm0 с самим собой?

//----------------start of for loop----------------
call    readTSC
movq    %rax, -32(%rbp)
movl    $0, -4(%rbp)
jmp .L4
.L6:
cvtsi2sd    -4(%rbp), %xmm1
// 1. use sqrtsd instruction
sqrtsd  %xmm1, %xmm0
ucomisd %xmm0, %xmm0
jp  .L8
je  .L5
.L8:
movapd  %xmm1, %xmm0
// 2. use C funciton call
call    sqrt
.L5:
movsd   -16(%rbp), %xmm1
addsd   %xmm1, %xmm0
movsd   %xmm0, -16(%rbp)
addl    $1, -4(%rbp)
.L4:
movl    -4(%rbp), %eax
cmpl    -36(%rbp), %eax
jl  .L6
//----------------end of for loop----------------
call    readTSC

Ответы

Ответ 1

Он использует библиотеку sqrt для обработки ошибок. См. Документацию glibc: 20.5.4 Отчеты об ошибках с помощью математических функций: математические функции устанавливаются errno для совместимости с системами, которые не имеют исключения IEEE754 флаги. Связано: glibc math_error(7) man page.

В качестве оптимизации он сначала пытается выполнить квадратный корень с помощью встроенной команды sqrtsd, а затем проверяет результат на себя с помощью инструкции ucomisd, которая устанавливает флаги следующим образом:

CASE (RESULT) OF
   UNORDERED:    ZF,PF,CF  111;
   GREATER_THAN: ZF,PF,CF  000;
   LESS_THAN:    ZF,PF,CF  001;
   EQUAL:        ZF,PF,CF  100;
ESAC;

В частности, сравнение QNaN с самим собой вернет UNORDERED, что вы получите, если попытаетесь взять квадратный корень из отрицательного числа. Это покрывает ветвь jp. Проверка je - это просто паранойя, проверяющая точное равенство.


Также обратите внимание, что gcc имеет параметр -fno-math-errno, который пожертвует этой обработкой ошибок для скорости. Этот параметр является частью -ffast-math, но может использоваться сам по себе, не позволяя оптимизаторам, изменяющим результат.

sqrtsd сам по себе правильно производит NaN для отрицательных и NaN-входов и устанавливает флаг IEEE754 Invalid. Проверка и ветвь должны только сохранить семантику errno -setting, на которую не полагается большинство кода.

-fno-math-errno по умолчанию используется в Darwin (OS X), где математическая библиотека никогда не устанавливает errno, поэтому функции могут быть встроены без этой проверки.