39

我今天正在阅读有关研究人员发现 NVidia 的 Phys-X 库使用 x87 FP 与 SSE2的文章。显然,这对于速度胜过精度的并行数据集来说不是最理想的。但是,文章作者继续引用:

英特尔在 2000 年底推出 P4 后开始不鼓励使用 x87。自 2003 年 K8 以来,AMD 弃用了 x87,因为 x86-64 是在 SSE2 支持下定义的;威盛的 C7 自 2005 年起就支持 SSE2。在 64 位版本的 Windows 中,x87 在用户模式下被弃用,在内核模式下完全被禁止。自 2005 年以来,业内几乎所有人都推荐 SSE 而不是 x87,并且没有理由使用 x87,除非软件必须在嵌入式 Pentium 或 486 上运行。

我想知道这一点。我知道 x87 在内部使用 80 位扩展双精度来计算值,而 SSE2 没有。这对任何人都没有关系吗?这对我来说似乎很奇怪。我知道当我对平面中的点、线和多边形进行计算时,在进行减法时值可能会出乎意料地错误,并且由于缺乏精度,区域可能会塌陷并且线会相互混叠。我想,使用 80 位值与 64 位值会有所帮助。

这是不正确的吗?如果没有,如果 x87 被淘汰,我们可以使用什么来执行扩展的双 FP 操作?

4

4 回答 4

29

x87 最大的问题基本上是所有的寄存器操作都是在 80 位中完成的,而大多数时候人们只使用 64 位浮点数(即双精度浮点数)。发生的情况是,您将 64 位浮点数加载到 x87 堆栈中,然后将其转换为 80 位。您以 80 位对其进行一些操作,然后将其存储回内存,将其转换为 64 位。与仅使用 64 位完成所有操作相比,您将获得不同的结果,并且使用优化编译器可能非常难以预测一个值可能经过多少次转换,因此很难验证您是否获得了“做回归测试时正确”的答案。

另一个问题是 x87 使用寄存器堆栈,而 SSE 使用可单独访问寄存器。使用 x87,你有一堆额外的指令来操作堆栈,我想英特尔和 AMD 宁愿让他们的处理器使用 SSE 代码快速运行,而不是试图让那些额外的堆栈操作 x87 指令快速运行。

顺便说一句,如果你遇到不准确的问题,你会想看看文章“每个程序员应该知道的浮点运算知识”,然后可能使用任意精度的数学库(例如 GMP)来代替。

于 2010-07-10T15:43:45.497 回答
5

为了正确使用扩展精度数学,语言必须支持一种类型,该类型可用于存储中间计算的结果,并且可以替代产生这些结果的表达式。因此,给定:

void print_dist_squared(double x1, double y1, double x2, double y2)
{
  printf("%12.6f", (x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
}

应该有某种类型可用于捕获和替换常见的子表达式x2-x1and y2-y1,从而允许将代码重写为:

void print_dist_squared(double x1, double y1, double x2, double y2)
{
  some_type dx = x2-x1;
  some_type dy = y2-y1;
  printf("%12.6f", dx*dx + dy*dy);
}

不改变程序的语义。不幸的是,ANSI C 未能指定可用于some_type执行扩展精度计算的平台上的任何类型,因此将扩展精度类型的存在归咎于英特尔比归咎于 ANSI 拙劣的支持更为常见。

事实上,扩展精度类型在没有浮点单元的平台上的价值与在 x87 处理器上一样大,因为在这样的处理器上,像 x+y+z 这样的计算需要以下步骤:

  1. 将 x 的尾数、指数和可能的符号解包到单独的寄存器中(指数和符号通常可以“双重铺位”)
  2. 同样解压 y。
  3. 右移具有较低指数的值的尾数(如果有),然后添加或减去这些值。
  4. 如果 x 和 y 有不同的符号,则将尾数左移直到最左边的位为 1 并适当调整指数。
  5. 将指数和尾数打包回双格式。
  6. 解压那个临时结果。
  7. 打开包装 z。
  8. 右移具有较低指数的值的尾数(如果有),然后添加或减去这些值。
  9. 如果较早的结果和 z 有不同的符号,则将尾数左移直到最左边的位为 1 并适当调整指数。
  10. 将指数和尾数打包回双格式。

使用扩展精度类型将允许消除步骤 4、5 和 6。由于 53 位尾数太大而无法容纳少于四个 16 位寄存器或两个 32 位寄存器,因此使用 64 位尾数执行加法并不比使用 53 位尾数慢,因此使用扩展精度数学在支持适当类型以保存临时结果的语言中提供了更快的计算而没有任何缺点。没有理由指责英特尔提供的 FPU 可以以在非 FPU 芯片上也是最有效的方法来执行浮点数学运算。

于 2015-09-21T23:20:00.320 回答
3

另一个答案似乎表明使用 80 位精度是一个坏主意,但事实并非如此。它有时在防止不精确方面发挥着至关重要的作用,例如参见 W. Kahan 的著作。

如果您可以快速摆脱它,请始终使用 80 位中间算术。如果这意味着您必须使用 x87 数学,那么就这样做吧。对它的支持无处不在,只要人们继续做正确的事,它就会无处不在。

于 2015-09-21T15:39:06.883 回答
0

双精度比 f80 少 11 位(大约 2.5 个半字节/位),对于许多应用程序(主要是游戏)来说,它不会受到伤害。但是您将需要所有可用的准确性,例如太空计划或医疗应用程序。

当有人说 f80(并因此而气馁)在堆栈上运行时,这有点误导。FPU 寄存器和操作类似于堆栈操作,也许这就是让人困惑的地方。它实际上基于内存(加载/存储),而不是堆栈本身,例如,与 cdecl stdcall 之类的调用约定相比,它实际上通过堆栈传递参数。并没有错。

SSE 的最大优势实际上是序列化操作,一次 2、4、8 个值,具有许多可变操作。是的,您可以直接转移到寄存器,但最终还是会将这些值转移到内存中。

f80 的最大缺点是它的奇数 10 字节长,它会破坏对齐。您必须将它们对齐 16 才能更快地访问。但对于数组来说并不是真正可行的。

您仍然必须使用 fpu 进行三角函数和其他先验数学运算。对于 asm,有许多非常有趣和有用的 f80 技巧。

对于游戏和常规的简单应用程序(几乎所有),您可以只使用 double 而不会导致某人死亡。但是对于一些严肃的数学或科学应用程序,你就是不能放弃 f80。

于 2016-09-09T01:51:30.557 回答