gpt4 book ai didi

c - 是否有任何浮点密集型代码在任何基于 x86 的架构中产生位精确的结果?

转载 作者:行者123 更新时间:2023-12-04 11:07:28 30 4
gpt4 key购买 nike

我想知道使用浮点运算的 C 或 C++ 中的任何代码是否会在任何基于 x86 的体系结构中产生精确的结果,而不管代码的复杂性。

据我所知,自 Intel 8087 以来的任何 x86 架构都使用准备处理 IEEE-754 浮点数的 FPU 单元,而且我看不出任何原因为什么不同架构的结果会有所不同。但是,如果它们不同(即由于不同的编译器或不同的优化级别),是否有某种方法可以通过配置编译器来产生精确的结果?

最佳答案

目录:

  • C/C++
  • 汇编
  • 创建实现这一目标的真实软件。


  • 在 C 或 C++ 中:

    不,完全符合 ISO C11 和 IEEE 标准的 C 实现不能保证与其他 C 实现的位相同结果,即使是同一硬件上的其他实现。

    (首先,我将假设我们谈论的是正常的 C 实现,其中 doubleIEEE-754 binary64 format 等,即使 x86 上的 C 实现使用其他格式的 double 和实现是合法的FP 数学与软件仿真,并在 float.h 中定义限制。当并非所有 x86 CPU 都包含在 FPU 中时,这可能是合理的,但在 2016 年,这是 Deathstation 9000 领域。)

    相关:Bruce Dawson 的 Floating-Point Determinism 博客文章是对这个问题的回答。他的开篇很有趣(后面还有很多有趣的东西):

    Is IEEE floating-point math deterministic? Will you always get the same results from the same inputs? The answer is an unequivocal “yes”. Unfortunately the answer is also an unequivocal “no”. I’m afraid you will need to clarify your question.



    如果您正在思考这个问题,那么您肯定会想看看 the index to Bruce's series of articles 关于浮点数学,它是由 x86 上的 C 编译器实现的,通常还有 asm 和 IEEE FP。

    第一个问题 :只有 "basic operations": + - * / and sqrt are required to return "correctly rounded" results ,即 <= 0.5ulp 的错误,正确四舍五入到尾数的最后一位,因此结果是最接近精确结果的可表示值。
    pow()log()sin() 等其他数学库函数允许实现者在速度和准确性之间进行权衡。例如,glibc 通常偏向于准确性,并且在某些函数 IIRC 上比 Apple 的 OS X 数学库慢。另见 glibc's documentation of the error bounds for every libm function across different architectures

    但是等等,情况变得更糟 。即使只使用正确舍入的基本操作的代码也不能保证相同的结果。

    C 规则还允许在保持更高精度的临时变量方面具有一定的灵活性。该实现定义了 FLT_EVAL_METHOD 以便代码可以检测它是如何工作的,但是如果您不喜欢该实现的功能,您就没有选择。您确实可以选择(使用 #pragma STDC FP_CONTRACT off )来禁止编译器例如将 a*b + c 转换为 FMA,在添加之前没有对 a*b 临时四舍五入。

    在 x86 上,针对 32 位非 SSE 代码(即 使用过时的 x87 指令 )的编译器通常在操作之间将 FP 临时文件保存在 x87 寄存器中。这会产生 80 位精度的 FLT_EVAL_METHOD = 2 行为。 (标准规定在每次赋值时仍然会进行舍入,但是像 gcc 这样的真正编译器实际上不会为舍入进行额外的存储/重新加载,除非您使用 -ffloat-store 。参见 https://gcc.gnu.org/wiki/FloatingPointMath 。标准的那部分似乎是在假设非优化的情况下编写的编译器或硬件有效地提供舍入到类型宽度,如非 x86,或像 x87 精度设置为舍入到 64 位 double 而不是 80 位 long double 。在每个语句 is exactly what gcc -O0 and most other compilers do 之后存储,并且标准允许额外的精度一个表达式的评估。)

    因此,当以 x87 为目标时,编译器可以使用两个 x87 float 指令计算三个 FADD 的总和,而无需将前两个的总和四舍五入为 32 位 float 。在这种情况下,临时文件具有 80 位精度......或者是吗?并非总是如此,因为 C 实现的启动代码(或 Direct3D 库!!!)可能已更改 x87 控制字中的精度设置,因此 x87 寄存器中的值四舍五入为 53 或 24 位尾数。 (这使得 FDIV 和 FSQRT 运行得更快一些。)所有这些都来自 Bruce Dawson's article about intermediate FP precision)

    在组装中:

    在舍入模式和精度设置相同的情况下,我认为每个 x86 CPU 都应该为相同的输入提供位相同的结果,即使对于像 FSIN 这样的复杂 x87 指令也是如此。

    英特尔的手册并没有准确定义每种情况下的结果,但我认为英特尔旨在实现位精确的向后兼容性。例如,我怀疑他们是否会为 FSIN 添加扩展精度范围缩减。它使用您通过 fldpi 获得的 80 位 pi 常数(正确舍入的 64 位尾数,实际上是 66 位,因为确切值的下 2 位为零)。英特尔关于最坏情况错误的文档相差 1.3 quintillion until they updated it after Bruce Dawson noticed how bad the worst-case actually was 。但这只能通过降低扩展精度范围来解决,因此在硬件上不会便宜。

    我不知道 AMD 是否实现了他们的 FSIN 和其他微编码指令以始终为英特尔提供位相同的结果,但我不会感到惊讶。我想有些软件确实依赖它。

    由于 SSE 只提供了 add/sub/mul/div/sqrt 的说明,所以没什么好说的。它们准确地实现了 IEEE 操作,因此任何 x86 实现都不可能给您任何不同的东西(除非舍入模式设置不同,或者非正规数为零和/或清零不同并且您有任何非正规)。

    SSE rsqrt(快速近似倒数平方根) is not exactly specified ,我认为即使在牛顿迭代之后你也有可能得到不同的结果,但 除了 SSE/SSE2 在asm 中总是有点精确,假设不是 MX设置奇怪。所以唯一的问题是让编译器生成相同的代码,或者只是使用相同的二进制文件。

    在真实生活中:

    因此,如果您静态链接使用 SSE/SSE2 的 libm 并分发这些二进制文件,它们将在任何地方运行。除非该库使用运行时 CPU 检测来选择替代实现...

    正如@Yan Zhou 指出的那样,您几乎需要将实现的每一点都控制到 asm,以获得精确的结果。

    然而,一些游戏确实依赖于多人游戏,但通常会检测/纠正不同步的客户端 。每个客户端都计算接下来会发生什么,而不是每一帧都通过网络发送整个游戏状态。如果游戏引擎被小心地实现为确定性的,它们就会保持同步。

    在 Spring RTS 中, clients checksum their gamestate to detect desync 。我已经有一段时间没玩了,但我确实记得至少 5 年前读过一些关于他们试图通过确保所有 x86 构建使用 SSE 数学,甚至 32 位构建来实现同步的内容。

    某些游戏不允许在 PC 和非 x86 控制台系统之间进行多人游戏的一个可能原因是引擎在所有 PC 上给出相同的结果,但在具有不同编译器的不同架构控制台上给出不同的结果。

    进一步阅读:GAFFER ON GAMES: Floating Point Determinism 。真实游戏引擎用于获得确定性结果的一些技术。例如将 sin/cos/tan 包装在未优化的函数调用中,以强制编译器将它们保留为单精度。

    关于c - 是否有任何浮点密集型代码在任何基于 x86 的架构中产生位精确的结果?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27149894/

    30 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com