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

标签 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

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

为什么汇编代码中有两个sqrt,一个使用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 函数进行错误处理。请参阅 glibc 的文档:20.5.4 Error Reporting by Mathematical Functions : 数学函数设置 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设置语义。

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

关于c - 为什么编译器会在编译的汇编代码中生成额外的 sqrts,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47138930/

相关文章:

gcc - 什么是计算机科学中的目标架构?

linux - 如何在 Linux 内核中打印出寄存器?

c - 如何将混合类型数组转换为 char 数组并返回?

c++ - 尝试构建 C++ 程序时收到错误消息 "Template with C linkage"

c - 虽然循环不等待输入 scanf ("%d")

c++ - 如何使用 gcc 和 intel 静态库进行编译?

assembly - C64汇编存储内存地址并增加它

linux - ELF header ,偏移量 06h 和 14h 是否重复?

c - 指针和字符串的关系

c - c中数组中的峰值元素