Суммирование vec4 [idx [i]] * scalar [i] с векторными регистрами YMM - PullRequest
3 голосов
/ 15 мая 2019

Я пытаюсь оптимизировать следующее sum{vec4[indexarray[i]] * scalar[i]}, где vec4 - это float[4], а scalar - это число с плавающей точкой. С 128-битными регистрами это сводится к

sum = _mm_fmadd_ps(
            _mm_loadu_ps(vec4[indexarray[i]]),
            _mm_set_ps1(scalar[i]),
            sum);

Если я хочу выполнить FMA на 256-битных регистрах, мне придется сделать что-то вроде

__m256 coef = _mm256_set_m128(
                    _mm_set_ps1(scalar[2 * i + 0]),
                    _mm_set_ps1(scalar[2 * i + 1]));
__m256 vec = _mm256_set_m128(
                    _mm_loadu_ps(vec4[indexarray[2 * i + 0]]),
                    _mm_loadu_ps(vec4[indexarray[2 * i + 1]]));
sum = _mm256_fmadd_ps(vec, coef, sum);

вместе с перемешиванием и добавлением в конце для суммирования верхней и нижней полос.

Теоретически, я получаю 5 в задержке (при условии архитектуры Haswell) от единственного FMA, но теряю 2x3 в задержке от _mm256_set_m128.

Есть ли способ сделать это быстрее, используя регистры ymm, или все выгоды от одного FMA будут с интересом компенсированы объединением регистров xmm?

1 Ответ

3 голосов
/ 15 мая 2019

, но теряют 2x3 в латентности от _mm256_set_m128

Нет, эта задержка не на критическом пути; это часть подготовки ввода для FMA. Задача создания большего числа тасов для каждого независимого значения i равна пропускная способность .

Задержка действительно имеет значение только для цепочки зависимостей, передаваемых в цикле через sum, которая является как входом, так и выходом из FMA.

Входные данные, которые зависят только от i и содержимого памяти, могут обрабатываться параллельно на нескольких итерациях путем выполнения не по порядку.


Вы , возможно, еще впереди, хотя и строите 256-битные векторы. Однако, если вы напишите исходный код (_mm256_set_m128 не является реальной инструкцией), он, вероятно, станет узким местом на входе или пропускной способности по частоте. Вы хотите, чтобы он компилировался до 128-битной загрузки, а затем vinsertf128 ymm, ymm, [mem], 1 для вставки старшей половины вектора. vinsertf128 стоит случайного упопа.

Если вы ограничены в задержке с 128-битными регистрами, вам может быть лучше использовать несколько аккумуляторов, чтобы (на Haswell) до 10 FMA могло быть в полете одновременно: задержка 5c * пропускная способность 0.5c. Добавьте их в конце. Почему Мулсс занимает всего 3 цикла в Haswell, в отличие от таблиц инструкций Агнера?

...