问题 生成的浮点比较装配的差异 =


我正在尝试生成的程序集,发现了一件有趣的事情。 有两个函数执行相同的计算。它们之间的唯一区别在于结果总和的方式。

#include <cmath>

double func1(double x, double y)
{
  double result1;
  double result2;

  if (x*x < 0.0) result1 = 0.0;
  else
  {
    result1 = x*x+x+y;
  }

  if (y*y < 0.0) result2 = 0.0;
  else
  {
    result2 = y*y+y+x;
  }

  return (result1 + result2) * 40.0;
}

double func2(double x, double y)
{
  double result = 0.0;

  if (x*x >= 0.0)
  {
    result += x*x+x+y;
  }

  if (y*y >= 0.0)
  {
    result += y*y+y+x;
  }

  return result * 40.0;
}

由x86 clang 3.7生成的程序集 -O2 打开 gcc.godbolt.org 还有那么多不同和意想不到的事情。 (在gcc上编译导致类似的程序集)

    .LCPI0_0:
    .quad   4630826316843712512     # double 40
func1(double, double):                             # @func1(double, double)
    movapd  %xmm0, %xmm2
    mulsd   %xmm2, %xmm2
    addsd   %xmm0, %xmm2
    addsd   %xmm1, %xmm2
    movapd  %xmm1, %xmm3
    mulsd   %xmm3, %xmm3
    addsd   %xmm1, %xmm3
    addsd   %xmm0, %xmm3
    addsd   %xmm3, %xmm2
    mulsd   .LCPI0_0(%rip), %xmm2
    movapd  %xmm2, %xmm0
    retq

.LCPI1_0:
    .quad   4630826316843712512     # double 40
func2(double, double):                             # @func2(double, double)
    movapd  %xmm0, %xmm2
    movapd  %xmm2, %xmm4
    mulsd   %xmm4, %xmm4
    xorps   %xmm3, %xmm3
    ucomisd %xmm3, %xmm4
    xorpd   %xmm0, %xmm0
    jb  .LBB1_2
    addsd   %xmm2, %xmm4
    addsd   %xmm1, %xmm4
    xorpd   %xmm0, %xmm0
    addsd   %xmm4, %xmm0
.LBB1_2:
    movapd  %xmm1, %xmm4
    mulsd   %xmm4, %xmm4
    ucomisd %xmm3, %xmm4
    jb  .LBB1_4
    addsd   %xmm1, %xmm4
    addsd   %xmm2, %xmm4
    addsd   %xmm4, %xmm0
.LBB1_4:
    mulsd   .LCPI1_0(%rip), %xmm0
    retq

func1 编译成无分支的程序集,涉及的指令少得多 func2。从而 func2 预计会慢得多 func1

有人可以解释这种行为吗?


6130
2017-11-05 15:01


起源

最明显的区别是优化器知道 x*x<0.0 可以优化到 false 但不知道 x*x>=0.0 可以优化到 true。从几个编译器看很多优化的汇编时,我从未见过优化器所做或不理解的任何理智模式。 - JSF


答案:


比较运算符的这种行为的原因 < 要么 >= 是不是你的 double 是 NaN 或不是 NaN。其中一个操作数的所有比较 NaN 返回 false。所以你的 x*x < 0.0 将 总是 无论是否是假的都是假的 x 是 NaN 或不。因此编译器可以安全地优化它。但是,的情况 x * x >= 0 会有不同的表现 NaN 和非NaN 值,因此编译器在程序集中保留条件跳转。

这是什么 cppreference 关于与涉及的NaN进行比较的说法:

转换后的操作数值按通常的数学意义进行比较(除了正负零比较相等,任何涉及NaN值的比较返回零)


15
2017-11-05 15:18