为什么编译器会在编译的汇编代码中生成额外的sqrts

时间:2015-04-24 17:51:14

标签: c gcc assembly x86-64 compiler-optimization

我试图使用以下简单的C代码来分析计算sqrt所需的时间,其中readTSC()是读取CPU的循环计数器的函数。

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
在英特尔机器上,结果(如下所示)令人惊讶?

为什么汇编代码中有两个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 个答案:

答案 0 :(得分:23)

它使用库sqrt函数进行错误处理。请参阅glibc的文档:20.5.4 Error Reporting by Mathematical Functions:数学函数设置errno,以便与不具有IEEE754异常标志的系统兼容。相关:glibc&#39; math_error(7)手册页。

作为优化,它首先尝试通过内联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分支涵盖。 -ffast-math检查只是偏执,检查确切的相等。

另请注意,gcc有一个-fno-math-errno option会牺牲此错误处理速度。此选项是sqrtsd的一部分,但可以单独使用,而不启用任何更改结果的优化。

errno本身正确地为负和NaN输入生成NaN,并设置IEEE754无效标志。检查和分支仅 以保留大多数代码不依赖的-fno-math-errno - 设置语义。

errno是Darwin(OS X)的默认设置,数学库从不设置overflow-y:scroll,因此无需检查即可内联函数。