Это выглядит нормально; добавление чисел в другом порядке приводит к разным округлениям во временных значениях.
FP математика не ассоциативна; оптимизация, как будто это изменит результаты. 1 Являются ли сложения с плавающей запятой и умножение ассоциативными? / Являются ли операции с плавающей запятой в C ассоциативными?
Количество изменений зависит от данных. Различия только в 5-м десятичном знаке кажутся разумными для float
.
Если вы не предприняли специальных численных мер предосторожности, таких как сложение первых чисел, результат в последовательном порядке не является «более правильным», они просто имеют разные ошибки.
Фактически, при использовании нескольких аккумуляторов обычно увеличивает точность для больших списков, предполагая, что все ваши числа имеют одинаковую величину. (В идеале несколько векторов SIMD, каждый из которых состоит из нескольких элементов, чтобы скрыть задержку FP-add или FMA).
https://en.wikipedia.org/wiki/Pairwise_summation - числовая техника, которая выводит это на следующий уровень: суммирование подмножеств списка в дереве, чтобы избежать добавления отдельных элементов массива к гораздо большему значению. См., Например, Как избежать менее точной суммы для массивов с несколькими столбцами
Использование фиксированного количества аккумуляторов (например, 8x __m256
= 64 float
аккумуляторов) может уменьшить ожидаемую ошибку в 64 раза вместо N до log N для полного попарного суммирования.
Сноска 1: Ассоциативность необходима для распараллеливания, SIMD и нескольких аккумуляторов. Ассоциативность дает нам возможность распараллеливания. Но что дает коммутативность?
На машине, например, с 4-тактовой задержкой FMA с пропускной способностью 2 на такт, с шириной SIMD 8 поплавков, т. Е. Системой Skylake с AVX2, потенциальное ускорение 4 * 2 = 8 от нескольких аккумуляторов, * 8 от ширины SIMD, умноженного на количество ядер, по сравнению с чисто последовательной версией, даже для задач, где может быть менее точным, чем просто другим.
Большинство людей считают, что фактор 8*8 = 64
стоит того! (И теоретически вы можете распараллелить для еще одного множителя, равного 4, на четырехъядерном процессоре, предполагая идеальное масштабирование для больших матриц).
Вы уже используете float
вместо double
для производительности.
См. Также Почему mulss занимает только 3 цикла в Haswell, в отличие от таблиц инструкций Агнера? для получения дополнительной информации об использовании нескольких аккумуляторов, чтобы скрыть задержку FMA в сокращении, выставляя этот другой фактор ускорения в 8 раз.
Кроме того, не используйте hadd
внутри самого внутреннего цикла. Суммируйте по вертикали и используйте эффективное сокращение в конце цикла. ( Самый быстрый способ сделать горизонтальную векторную сумму с плавающей запятой на x86 ). Вы действительно хотите избежать того, чтобы компилятор извлекал ваши векторы для скалярного вычисления на каждом шагу, что лишает большинство преимуществ SIMD! Помимо того, что hadd
не стоит использовать для горизонтальных сумм 1 вектора; стоит 2 шаффла + обычный add
на всех существующих процессорах.