为什么我的 C++ 程序从 long double 切换到 float128 时如此缓慢?

Tho*_*est 2 c++ floating-point x86 long-double quadruple-precision

我在 Unix 上编程,使用 g++ 4.8.2 编译器。我目前需要将此时使用的 C++ 程序long double(在我的情况下为 64 位有效位)转换为使用该__float128类型(有效位为 113 位)的程序。我使用了libquadmath0包和 boost 库来做到这一点,但生成的程序比使用long double.

这令人困惑,因为有效数的大小并没有大多少,而且从 切换double到时我没有观察到这种差异long double。这种时间差异是否正常,如果不正常,我该如何解决?

编码:

#include <stdlib.h>
#include <stdint.h>
#include <stdio.h>
#include <math.h>
#include <complex.h>
extern "C" {
#include <quadmath.h>
}
#include <gmp.h>
#include <iomanip>
#include <cfloat>
#include <boost/multiprecision/float128.hpp>


using namespace boost::multiprecision;
using namespace std;

typedef __float128 long_double_t;

void main()
{
...
}
Run Code Online (Sandbox Code Playgroud)

编译说明:

g++ --std=c++11 main.cc -o main -lgmp -lquadmath -Ofast -m64
Run Code Online (Sandbox Code Playgroud)

phu*_*clv 5

这是令人困惑的,因为有效数的大小并没有高很多,而且在从 切换double到时我没有观察到这种差异long double

举个简单的例子:用一个 12 位的袖珍计算器将两个 8 位数字相加,然后将两个 11 位数字相加。你看得到差别吗?现在用那个计算器把两个 23 位数字相加,你认为哪个会更慢?显然最后一个需要更多的操作(还有空间,因为你需要将中间结果写到纸上)

在 x86 中,您有对 IEEE-754 单、双和80 位扩展精度的硬件支持,因此对这些类型的操作完全在硬件中完成,通常只是一条指令。没有来自不同,这是相同的中的x87指令。如果你使用 SSE 那么会比由于使用新的 SIMD 寄存器和指令快一点 long doubledouble + doublelong double + long doubleFADDdoublelong double

__float128但是,当您使用时,编译器需要使用慢得多的软件仿真。您不能long double用 2 条指令添加 2 个值。您需要手动完成所有操作:

  • 打破符号、指数和有效数部分(至少 ~3 条指令)。有效数必须存储在多个寄存器中,因为您没有这么大的单个整数寄存器
  • 对齐2个值的小数点位置,这需要很多移位和掩码操作(再次因为有效数存储在多个寄存器中)
  • 添加2个有效数,在64位平台上需要2条指令
  • 对结果进行归一化,这需要检查上溢/下溢条件的总和,找到最高有效位的位置,计算指数...
  • 组合结果的符号、指数和有效数

这些步骤包括几个分支(可能导致分支预测错误)、内存加载/存储(因为 x86 没有很多寄存器)以及更多最终加起来至少有几十条指令的事情。将这些复杂任务的速度降低 10 倍已经是一项了不起的成就。而且我们还没有进行乘法,当有效位宽度加倍时,乘法难度增加了 4 倍。除法、平方根、求幂、三角……要复杂得多,而且速度会慢很多