已阅读此 question我有理由相信,使用具有相同输入(在相同硬件上,使用相同编译器编译)的浮点算术的给定过程应该是确定性的。我正在查看一个不正确的案例,并试图确定可能导致这种情况的原因。
我已经编译了一个可执行文件并且我正在为它提供完全相同的数据,在一台机器上运行(非多线程)但是我得到了大约 3.814697265625e-06 的错误,经过仔细的谷歌搜索我发现实际上是等于 1/4^9 = 1/2^18 = 1/262144。这非常接近 32 位 float 的精度级别(根据维基百科,大约为 7 位数)
我怀疑它与已应用于代码的优化有关。我正在使用英特尔 C++ 编译器并将浮点推测转换为快速而不是安全或严格。这会使浮点过程变得不确定吗?是否有其他优化等可能导致这种行为?
编辑:根据 Pax 的建议,我重新编译了代码,其中浮点推测变为安全,现在我得到了稳定的结果。这使我能够澄清这个问题 - 浮点推测实际上做了什么,当应用于完全相同的输入时,这如何导致相同的二进制文件(即一次编译,多次运行)生成不同的结果?
@Ben 我正在使用英特尔(R) C++ 11.0.061 [IA-32] 进行编译,并且在英特尔四核处理器上运行。
最佳答案
几乎在任何存在快速模式和安全模式的情况下,您都会发现某种权衡。否则一切都会以快速安全模式运行:-)。
而且,如果您使用相同的输入得到不同的结果,则您的过程不是确定性的,无论您多么相信它(尽管有经验证据)。 p>
我会说你的解释是最有可能的。将其置于安全模式并查看不确定性是否消失。这会告诉你肯定的。
至于是否有其他优化,如果您使用相同的编译器/链接器和这些工具的相同选项在相同的硬件上编译,它应该生成相同的代码。除了快速模式(或由于宇宙射线而导致内存中的比特腐烂,但这种可能性很小),我看不到任何其他可能性。
更新后:
Intel 有文档here这解释了他们在安全模式下不允许做的一些事情,包括但不限于:
- 重新关联:
(a+b)+c -> a+(b+c)
。 - 零折叠:
x + 0 -> x, x * 0 -> 0
。 - 倒数乘法:
a/b -> a*(1/b)
。
虽然您说这些操作是编译时定义的,但英特尔芯片非常聪明。他们可以重新排序指令以在多 CPU 设置中保持管道充满,因此,除非代码明确禁止这种行为,否则事情可能会在运行时(而不是编译时)发生变化以保持全速运行。
有关矢量化的链接文档的第 15 页(“问题:不同的结果在同一处理器上对相同数据重新运行相同的二进制文件”)对此进行了(简要)介绍。
我的建议是决定您是需要原始的咕噜声还是结果的完全可重复性,然后根据此选择模式。
关于c++ - 什么可能导致确定性过程产生浮点错误,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/968435/