С плавающей точкой является детерминированным. Одни и те же операции с плавающей запятой, выполняемые на одном и том же оборудовании, всегда дают один и тот же результат. Там нет черной магии, шума, случайности, размытости или каких-либо других вещей, которые люди обычно приписывают плавающей точке. Зубная фея не появляется, возьмите низкие результаты и оставьте четверть под подушкой.
Теперь уже говорилось, что некоторые заблокированные алгоритмы, которые обычно используются для крупномасштабных параллельных вычислений , являются недетерминированными с точки зрения порядка, в котором выполняются вычисления с плавающей запятой, что может привести к -бит-точные результаты по пробегам.
Что вы можете с этим поделать?
Во-первых, убедитесь, что вы на самом деле не можете жить с ситуацией. Многое из того, что вы можете попытаться навести порядок в параллельных вычислениях, снизит производительность. Вот только как это.
Я бы также отметил, что хотя заблокированные алгоритмы могут вносить некоторую степень недетерминизма, они часто дают результаты с меньшими ошибками округления, чем наивные неблокированные последовательные алгоритмы (удивительно, но верно!). Если вы можете жить с ошибками, вызванными наивным последовательным алгоритмом, вы, вероятно, можете жить с ошибками параллельного заблокированного алгоритма.
Теперь, если вам действительно нужна точная воспроизводимость при разных запусках, вот несколько советов, которые не слишком сильно влияют на производительность:
Не используйте многопоточные алгоритмы, которые могут переупорядочивать вычисления с плавающей точкой. Задача решена. Это не означает, что вы не можете использовать многопоточные алгоритмы вообще, просто вам нужно убедиться, что каждый отдельный результат затрагивается только одним потоком между точками синхронизации. Обратите внимание, что на самом деле это может улучшить производительность на некоторых архитектурах, если все сделано правильно, за счет уменьшения конкуренции за D $ между ядрами.
В операциях сокращения можно заставить каждый поток сохранять свой результат в индексируемом месте в массиве, ждать завершения всех потоков, собирать элементы массива в порядке. Это добавляет небольшие накладные расходы памяти, но, как правило, довольно терпимо, особенно когда число потоков «мало».
Найдите способы поднять параллелизм. Вместо вычисления 24 матричных умножений, каждое из которых использует параллельные алгоритмы, параллельно вычисляют 24 матричных произведения, каждое из которых использует последовательный алгоритм. Это также может быть полезно для производительности (иногда очень сильно).
Есть много других способов справиться с этим. Все они требуют мысли и заботы. Параллельное программирование обычно делает.