已经读过这篇文章 问题 我相当确定,使用具有相同输入(在相同硬件上,使用相同编译器编译)的浮点算术的给定进程应该是确定性的。我正在研究一个事实并非如此的案例,并试图确定可能导致此情况的原因。

我已经编译了一个可执行文件,并且向它提供了完全相同的数据,在单台机器(非多线程)上运行,但我收到了大约 3.814697265625e-06 的错误,经过仔细谷歌搜索后,我发现它实际上等于 1 /4^9 = 1/2^18 = 1/262144。这非常接近 32 位浮点数的精度水平(根据维基百科大约为 7 位数字)

我怀疑这与应用于代码的优化有关。我正在使用英特尔 C++ 编译器,并将浮点推测转换为快速而不是安全或严格。这会使浮点过程变得不确定吗?是否有其他优化等可能导致这种行为?

编辑:根据 Pax 的建议,我重新编译了代码,浮点推测变得安全,现在我得到了稳定的结果。这使我能够澄清这个问题 - 浮点推测实际上做了什么以及这如何导致相同的二进制文件(即一次编译,多次运行)在应用于完全相同的输入时生成不同的结果?

@Ben 我正在使用 Intel(R) C++ 11.0.061 [IA-32] 进行编译,并且在 Intel 四核处理器上运行。

有帮助吗?

解决方案

几乎在任何存在快速模式和安全模式的情况下,您都会发现某种权衡。否则一切都会以快速安全模式运行:-)。

而且,如果您使用相同的输入得到不同的结果,您的过程是 不是 确定性的,无论你多么相信它(尽管有经验证据)。

我想说你的解释是最有可能的。将其置于安全模式,看看非确定性是否消失。那会肯定地告诉你。

至于是否还有其他优化,如果您使用相同的编译器/链接器在相同的硬件上进行编译 以及这些工具的相同选项, ,它应该生成相同的代码。除了快速模式之外,我看不到任何其他可能性(或者由于宇宙射线而导致内存中的位腐烂,但这不太可能)。

您更新后:

英特尔有一个文档 这里 这解释了他们在安全模式下不允许做的一些事情,包括但不限于:

  • 重新关联: (a+b)+c -> a+(b+c).
  • 零折叠: x + 0 -> x, x * 0 -> 0.
  • 倒数相乘: a/b -> a*(1/b).

虽然您声明这些操作是编译时定义的,但英特尔芯片非常聪明。他们可以重新排序指令,以在多 CPU 设置中保持管道满载,因此,除非代码明确禁止此类行为,否则事情可能会在运行时(而不是编译时)发生变化,以保持全速运行。

该链接文档的第 15 页(简要)介绍了这一点,该文档讨论了矢量化(“问题:在同一处理器上的相同数据上重新运行相同的二进制文件会产生不同的结果”).

我的建议是决定您是否需要原始的咕噜声或结果的完全可再现性,然后基于此选择模式。

其他提示

如果你的程序被并行化的,因为它可能是在一个四核运行,那么它可能是不确定的。

假设你有4个处理器添加一个浮点值到相同的存储器位置。然后,你可能会得到

(((InitialValue+P1fp)+P2fp)+P3fp)+P4fp

(((InitialValue+P2fp)+P3fp)+P1fp)+P4fp

或任何其他可能的顺序的。

哎呀,你甚至可能获得

 InitialValue+(P2fp+P3fp)+(P1fp+P4fp)

如果编译器是不够好。

不幸的是,浮点加法是不可交换的或缔合。实数运算是,但浮点是因为舍入,上溢和下溢的没有,。

由于这个原因,平行FP计算往往是非确定性的。 “经常”,因为那看起来像

节目
  on each processor
    while( there is work to do ) {
       get work
       calculate result
       add to total 
    }

将是非确定性的,因为时间,每个需要可以广泛地变化量 - 你无法预测操作的顺序。 (如果更糟糕的螺纹相互作用。)

但不总是,因为有样式并行编程的是确定性的。

当然,什么谁在乎决定很多人做的是在整数工作或固定点来避免此问题。我特别喜欢superaccumulators,512,1024,或浮点数可以被添加到2048比特数,而不遭受舍入误差。


作为用于单线程应用:编译器可以重新排列码。不同的编译可能会给出不同的答案。但是,任何特定的二进制应该是确定性的。

除非......你是在一个动态语言工作。执行该重新排序FP计算,随时间变化的optimizatuions。

或者除非...真长镜头:安腾有一些功能,如ALAT,这使即使单线程编码的不确定性。你不可能通过这些受到影响。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top