Параллелизм: слегка отличающиеся результаты с плавающей запятой? - PullRequest
9 голосов
/ 16 апреля 2011

Я пытаюсь отладить свою библиотеку параллелизма для языка программирования D.Недавно было зарегистрировано сообщение об ошибке , которое указывает на то, что младшие биты некоторых операций с плавающей запятой, которые выполняются с помощью задач, являются недетерминированными при выполнении.(Если вы читаете отчет, обратите внимание, что параллельное сокращение работает под капотом, создавая задачи детерминированным способом.)

Это не похоже на проблему режима округления, потому что я пытался установить режим округления вручную,Я также уверен, что это не ошибка параллелизма.Библиотека хорошо протестирована (включая прохождение стресс-теста 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, я получаю одинаковый ответ для обоих потоков.

ОТВЕТ : оказывается, что основная причина заключается в том, что состояние с плавающей запятой по-разному инициализируется наосновной поток, чем в других потоках в Windows в D. См. сообщение об ошибке, которое я только что подал.

1 Ответ

2 голосов
/ 16 апреля 2011

Вот статья , в которой объясняется множество причин, по которым один и тот же код C может привести к несколько иным результатам.В вашем случае наиболее вероятной причиной является переупорядочение внутренних команд процессора.

Просто неправильно ожидать, что вычисления с плавающей точкой будут детерминированными вплоть до младших битов.Это не то, что числа с плавающей запятой были предназначены для выполнения.

...