【问题标题】:Parallelism: Subtly different floating point results?并行性:浮点结果略有不同?
【发布时间】:2011-04-16 00:10:07
【问题描述】:

我正在尝试为 D 编程语言调试我的并行库。 bug report was recently filed 表示使用任务执行的某些浮点运算的低位在运行中是不确定的。 (如果您阅读该报告,请注意并行化简通过以确定性方式创建任务在幕后工作。)

这似乎不是舍入模式问题,因为我尝试手动设置舍入模式。我也很确定这不是并发错误。该库经过良好测试(包括通过Jinx 压力测试),问题始终局限于低位,甚至在低级内存模型问题较少的单核机器上也会发生问题。浮点结果可能因调度操作的线程而异,还有哪些其他原因?

编辑:我在这里进行了一些 printf 调试,似乎各个任务的结果有时在运行时会有所不同。

编辑#2:下面的代码以更简单的方式重现了这个问题。它在主线程中对数组的项求和,然后启动一个新线程来执行完全相同的函数。问题绝对不是我的库中的错误,因为这段代码甚至没有使用我的库。

import std.algorithm, core.thread, std.stdio, core.stdc.fenv;

real sumRange(const(real)[] range) {
    writeln("Rounding mode:  ", fegetround);  // 0 from both threads.
    return reduce!"a + b"(range);
}

void main() {
    immutable n = 1_000_000;
    immutable delta = 1.0 / n;

    auto terms = new real[1_000_000];
    foreach(i, ref term; terms) {
        immutable x = ( i - 0.5 ) * delta;
        term = delta / ( 1.0 + x * x ) * 1;
    }

    immutable res1 = sumRange(terms);
    writefln("%.19f", res1);

    real res2;
    auto t = new Thread( { res2 = sumRange(terms); } );
    t.start();
    t.join();
    writefln("%.19f", res2);
}

输出:

舍入模式:0

0.7853986633972191094

舍入模式:0

0.7853986633972437348

另一个编辑

这是我以十六进制打印时的输出:

舍入模式:0

0x1.921fc60b39f1331cp-1

舍入模式:0

0x1.921fc60b39ff1p-1

此外,这似乎只发生在 Windows 上。当我在 Linux VM 上运行此代码时,两个线程都得到相同的答案。

ANSWER:事实证明,根本原因是浮点状态在主线程上的初始化方式与 D 中 Windows 上的其他线程不同。请参阅the bug report I just filed.

【问题讨论】:

  • 我确定你用谷歌搜索过,但这个链接描述了不同 CPU 架构可能产生不同浮点结果的几个原因(即使它们都实现了 IEEE 754)network-theory.co.uk/docs/gccintro/gccintro_70.html
  • @Matt:很抱歉造成误解。我说的是同一硬件上甚至同一物理 CPU 上的不同线程。如果您仍然使用多线程,我刚刚验证了在单核机器上会发生这种情况。
  • 您链接到的错误报告提到了关联性——这可能是您的问题的原因吗? (即,您不是每次都以相同的顺序添加相同的数字。)
  • @Mehrdad:除非我的代码中有一些细节从我的记忆中逃脱,否则总和顺序的东西是确定性的。如果线程数发生变化或其他情况,人们会期望结果略有不同,但对于相同的线程数/工作单元大小/等,则不会。
  • @dsimcha:嗯...你介意在这里发布确切的代码吗?

标签: floating-point d numerical parallel-processing


【解决方案1】:

这里有一个paper that explains 相同的 C 代码可能导致略有不同结果的许多原因。在您的情况下,最可能的原因是 CPU 内部指令重新排序。

期望浮点计算直到低位都是确定性的,这是完全错误的。这不是浮点数的设计初衷。

【讨论】:

  • 我已经浏览过这篇论文,我了解它所说的基本内容。但是,在这种情况下,我们讨论的是在相同硬件上运行的相同二进制代码(不仅仅是相同的源代码)。唯一的区别是它在哪个线程上运行。
  • 在快速浏览之后,我没有看到它在哪里谈到了 CPU 级指令重新排序。另外,据我了解,如果任何对 CPU 重新排序的指令确实导致了一个位的差异,那么它应该被视为 CPU 错误。 - OTOH 提到了编译器级别的相同类型的事情(代码中的微小变化导致不同的舍入位置等),这是一个真正的问题。 (顺便说一句,在 D/x86 中 real 是一个 80 位浮点数,因此数学永远不应该被截断。)
  • @dsimcha, @BCS:你说得对,我记错了(它只提到了编译器完成的指令重新排序)。但是由于涉及到多线程,那么差异是否是由于线程在内核之间移动时将 80 位寄存器值截断为 64 位?
  • 在上下文切换期间修剪 FP 寄存器的任何操作系统早就被报告为损坏。
猜你喜欢
  • 2015-12-01
  • 1970-01-01
  • 2019-10-29
  • 1970-01-01
  • 2013-01-31
  • 1970-01-01
  • 2021-04-19
  • 2017-07-05
相关资源
最近更新 更多