确定性模型的运行会产生小的,不可预测的结果


10

我有一个用C编写的相当大的模型(约5000行)。它是一个串行程序,在任何地方都没有随机数的产生。它将FFTW库用于使用FFT的函数-我不知道FFTW实现的详细信息,但是我假设其中的函数也是确定性的(如果我出错,请更正我)。

我无法理解的问题是,在同一台计算机(相同的编译器,相同的库)上运行相同的结果在结果上存在很小的差异。

我使用双精度变量,并将结果输出到变量中value,例如,我发出: fprintf(outFID, "%.15e\n", value);
fwrite(&value, 1, sizeof(double), outFID);

而且我会不断得到诸如以下的差异:
2.07843469652206 4 e-16与2.07843469652206 3 e-16

我花了很多时间试图找出原因。最初我以为我的一个存储芯片已经坏了,所以我下令更换了它们,无济于事。随后,我还尝试在同事的Linux机器上运行我的代码,并且得到了相同性质的差异。

是什么原因造成的?现在这是一个小问题,但我想知道这是否是“冰山一角”(一个严重的问题)。

我以为我会在这里发布而不是StackOverflow,以防有人使用数值模型。如果有人可以阐明这一点,我将非常有义务。

评论后续:
Christian Clason和Vikram:首先,感谢您对我的问题的关注。您链接的文章建议:1.舍入错误限制了准确性,并且2.不同的代码(例如引入看似无害的打印语句)可能会影响机器的结果。我要澄清的是,我没有比较效果fwritefprintf功能。我正在使用一个或另一个。特别是,两次运行都使用相同的可执行文件。我只是在说明是否使用fprintfOR 发生问题fwrite

因此,代码路径(和可执行文件)相同,硬件也相同。在所有这些外部因素保持不变的情况下,随机性从何而来呢?我怀疑由于错误的内存无法正确保留位而发生了位翻转,这就是为什么我更换了内存芯片的原因,但是我证实这并不是这里的问题,我证实并指出。我的程序在一次运行中输出了数千个这种双精度数字,并且总是有随机的少数具有随机的位翻转。

跟帖基督教克拉森的第一个评论:为什么是机器精度内一样的0?双精度数的最小正数是2.22e-308,那不等于0吗?我的程序输出10 ^ -16范围(从1e-15到8e-17)的数千个值,并且我们一直在研究项目中看到有意义的变化,所以我希望我们不要一直在研究荒谬的东西。数字。210-16

后续活动2
这是模型输出的时间序列的图,有助于注释中的分支讨论。 在此处输入图片说明


210-16

您在问为什么您的机器不比机器精度更精确。en.wikipedia.org/wiki/Machine_epsilon
维克拉姆

1
有关代码路径对浮点算法的微妙影响的相关示例,请参见inf.ethz.ch/personal/gander/Heisenberg/paper.html。而且,当然,ece.uwaterloo.ca/~dwharder/NumericalAnalysis/02Numerics/Double/...
基督教克拉森

1
10-16

2
1个

Answers:


9

现代计算系统的某些方面本质上是不确定的,可能导致此类差异。只要与解决方案所需的精度相比差异很小,那么就没有任何理由担心此问题。

根据我自己的经验,可能出问题的示例。考虑计算两个向量x和y的点积的问题。

d=一世=1个ñX一世ÿ一世

X一世ÿ一世

例如,您可能首先计算两个向量的乘积为

d=X1个ÿ1个+X2ÿ2+X3ÿ3

然后作为

d=X1个ÿ1个+X2ÿ2+X3ÿ3

这怎么可能发生?这有两种可能性。

  1. 并行内核上的多线程计算。现代计算机通常具有2个,4个,8个甚至更多个可以并行工作的处理器内核。如果您的代码正在使用并行线程在多个处理器上计算点积,那么系统的任何随机扰动(例如,用户移动了鼠标,并且处理器核心之一必须在返回点积之前处理鼠标的移动)导致添加顺序发生变化。

  2. 数据和矢量指令的初始化。现代的英特尔处理器具有一组特殊的指令,这些指令可以一次(例如)对浮点数进行运算。如果数据在16个字节的边界上对齐,则这些矢量指令的效果最佳。通常,点积循环会将数据分成16个字节的部分(一次4个浮点数)。如果第二次重新运行代码,则数据可能与16个字节的内存块不同地对齐,因此添加的内容以不同的顺序执行,导致不同的答案。

您可以通过使代码作为单个线程运行并禁用所有并行处理来解决点1。您可以通过要求内存分配来对齐内存块来解决第2点的问题(通常,您可以通过使用-align之类的开关来编译代码来完成此操作。)如果您的代码仍给出变化的结果,则还有其他可能在。

英特尔的文档讨论了可能导致英特尔数学内核库的结果不可再现的问题。 英特尔的另一份文档讨论了与英特尔的编译器一起使用的编译器开关。


我看到您认为您的代码运行单线程。尽管您可能很了解您的代码,但是如果您调用运行多线程的子例程(例如BLAS例程),我也不会感到惊讶。您应该检查以查看正在使用的库。您还可以使用系统监视工具查看CPU使用率。
Brian Borchers

1
或是说FFTW库...
Christian

@BrianBorchers,谢谢。由浮点加法的非关联性质得出的随机性的例子是有启发性的。克里斯蒂安·克拉森(Christian Clason)提出了一个第二个问题,即考虑到数字的数量,我的模型输出是否有意义-如果他是正确的话(我正确理解了他)可能是一个主要问题,所以我现在在研究这个问题。
boxofchalk1 2016年

2

提到的FFTW库可能以非确定性模式运行。

如果使用的是FFTW_MEASURE或FFTW_PATIENT模式,则程序将在运行时检查哪些参数值工作最快,然后将在整个程序中使用这些参数。由于运行时间显然会略有波动,因此参数将有所不同,并且傅立叶变换的结果将不确定。如果要使用确定性FFTW,请使用FFTW_ESTIMATE模式。


1

诚然,由于多核/多线程处理方案的影响,表达式术语评估顺序的更改很可能发生,但是请不要忘记,即使有很长的路要走,也可能存在某种硬件设计缺陷。还记得奔腾FDIV问题吗?(请参阅https://en.wikipedia.org/wiki/Pentium_FDIV_bug)。前一段时间,我从事基于PC的模拟电路仿真软件的研究。我们的方法论的一部分涉及开发回归测试套件,我们将针对每晚构建的软件进行测试。利用我们开发的许多模型,可以采用迭代方法(例如Newton-Raphson(https://en.wikipedia.org/wiki/Newton%27s_method)和Runge-Kutta)在模拟算法中被广泛使用。对于模拟设备,通常会出现内部伪像,例如电压,电流等,其数值极小。作为模拟过程的一部分,这些值会随着(模拟)时间而逐渐变化。这些变化的幅度可能非常小,而且我们经常观察到的是,随后的FPU对此类增量值的操作与FPU精度的“噪声”阈值(64位浮点型有53位尾数IIRC)接壤。加上我们经常不得不在模型中引入“ PrintF”日志记录代码以允许进行调试(天天好!),实际上每天都会保证有零星的结果!所以呢' 这一切是什么意思?您必须期望在这种情况下能够看到差异,并且最好的办法是定义并实施一种决定(幅度,频率,趋势等)何时/如何忽略它们的方法。


谢谢吉姆的见解。关于什么基本现象会导致这种“内部伪像”的任何想法?我以为电磁干扰可能只是其中之一,但是随后重要的位也会受到影响,不是吗?
boxofchalk1 2016年

1

虽然可能是异步操作产生的浮点取整问题,但我怀疑这更平庸了。使用未初始化的变量会给确定性代码增加随机性。这是一个通常被开发人员忽略的常见问题,因为在调试模式下运行时,所有变量在声明时都初始化为0。当不在调试模式下运行时,分配给变量的内存具有分配前的内存值。作为优化,分配时内存不会归零。如果您的代码中发生这种情况,将很容易修复,而在库代码中则更容易解决。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.