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

Posted

技术标签:

【中文标题】为啥编译器在编译的汇编代码中会生成额外的 sqrts【英文标题】:Why does compiler generate additional sqrts in the compiled assembly code为什么编译器在编译的汇编代码中会生成额外的 sqrts 【发布时间】:2015-04-24 17:51:14 【问题描述】:

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

在 Intel 机器上,结果(如下所示)令人惊讶?

为什么汇编代码中有两个 sqrt,一个使用 sqrtsd 指令,另一个使用函数调用? 是否与循环展开和尝试在一次迭代中执行两个 sqrt 有关?

以及如何理解线

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

【问题讨论】:

那一定是未优化的代码。 Real code 正确布置了分支(在非 NaN 情况下没有采用分支),并省略了 je,因为它在 ucomisd 之后总是正确的。 【参考方案1】:

它使用库sqrt 函数进行错误处理。请参阅 glibc 的文档:20.5.4 Error Reporting by Mathematical Functions: math functions set errno 以了解与没有 IEEE754 异常标志的系统的兼容性。相关: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,如果您尝试取负数的平方根,您将得到此结果。这由jp 分支覆盖。 je 检查只是妄想症,检查是否完全相等。


还要注意,gcc 有一个-fno-math-errno option,它会为了速度而牺牲这种错误处理。此选项是 -ffast-math 的一部分,但可以单独使用,无需启用任何改变结果的优化。

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

-fno-math-errno 是 Darwin (OS X) 上的默认值,其中数学库从不设置 errno,因此无需此检查即可内联函数。

【讨论】:

请注意,`-ffast-math' 不仅仅是为了速度而牺牲错误处理。特别是,它还违反了 IEEE 754 合规性,即只有在您知道自己在做什么的情况下才能谨慎使用。另见***.com/questions/7420665/… @godfatherofpolka 是的,一般来说。但是在这种情况下,仅此而已。 是的,没错,我只是觉得每次提到快速数学标志都应该带有警告标签,这就是我添加该评论的原因。 @godfatherofpolka 很公平 :) @harold:-fno-math-errno 省去了测试,更安全。

以上是关于为啥编译器在编译的汇编代码中会生成额外的 sqrts的主要内容,如果未能解决你的问题,请参考以下文章

为啥要在汇编中编程? [关闭]

为啥从 constexpr 引用生成的汇编代码与 constexpr 指针不同?

查看C语言/C++编译器生成的汇编语言代码

如何使用 gcc 生成可以用 nasm 编译的汇编代码 [重复]

ucc编译器(汇编生成)

c#程序打包机器代码生成(Ngen.exe)