2

我在 Unix 上编程,使用 g++ 4.8.2 编译器。我目前需要将此时使用的 C++ 程序long double(在我的情况下有效位为 64 位)转换为使用该__float128类型的程序(有效位为 113 位)。我使用libquadmath0包和 boost 库来做到这一点,但生成的程序比 with 慢 10~20 倍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()
{
...
}

编译说明:

g++ --std=c++11 main.cc -o main -lgmp -lquadmath -Ofast -m64
4

1 回答 1

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 倍。除法、平方根、取幂、三角函数......要复杂得多,而且会慢得多

于 2021-03-24T16:39:21.117 回答