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

我正在尝试使用以下简单的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 

它使用库sqrt函数进行error handling。 请参阅glibc的文档: 20.5.4按数学函数报告错误 :数学函数设置errno以与没有IEEE754exception标志的系统兼容。 相关:glibc的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自身进行比较将返回UNORDERED ,如果您尝试取负数的平方根,则会得到UNORDERED 。 这由jp分支涵盖。 je检查只是偏执狂,检查确切的平等。


另请注意,gcc有一个-fno-math-errno选项 ,它会牺牲此error handling速度。 此选项是-ffast-math一部分,但可以单独使用而不启用任何结果更改优化。

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

-fno-math-errno是Darwin(OS X)的默认值,其中数学库从不设置errno ,因此可以在没有此检查的情况下内联函数。