Самый быстрый способ выполнить суммирование горизонтальных векторов с помощью инструкций AVX

У меня есть упакованный вектор из четырех 64-битных значений с плавающей запятой.
Я хотел бы получить сумму элементов вектора.

С SSE (и с использованием 32-битных чисел с плавающей запятой) я мог просто сделать следующее:

v_sum = _mm_hadd_ps(v_sum, v_sum);
v_sum = _mm_hadd_ps(v_sum, v_sum);

К сожалению, несмотря на то, что в AVX есть инструкция _mm256_hadd_pd, результат отличается от версии SSE. Я считаю, что это связано с тем, что большинство инструкций AVX работают как инструкции SSE для каждого младшего и высокого 128-битного кода отдельно, не пересекая 128-битную границу.

В идеале решение, которое я ищу, должно соответствовать следующим рекомендациям:
1) используйте только инструкции AVX / AVX2. (без SSE)
2) сделайте это не более чем в 2-3 инструкциях.

Однако любой эффективный / элегантный способ сделать это (даже без соблюдения приведенных выше рекомендаций) всегда приемлем.

Большое спасибо за любую помощь.

-Луиджи Кастелли


person Luigi Castelli    schedule 19.03.2012    source источник
comment
Начните с _mm256_extractf128_ps, _mm_add_ps двух половинок вместе, затем используйте существующие методы уменьшения вектора 128b.   -  person Peter Cordes    schedule 13.04.2016


Ответы (3)


Если у вас есть два __m256d вектора x1 и x2, каждый из которых содержит четыре double, которые вы хотите суммировать по горизонтали, вы можете сделать:

__m256d x1, x2;
// calculate 4 two-element horizontal sums:
// lower 64 bits contain x1[0] + x1[1]
// next 64 bits contain x2[0] + x2[1]
// next 64 bits contain x1[2] + x1[3]
// next 64 bits contain x2[2] + x2[3]
__m256d sum = _mm256_hadd_pd(x1, x2);
// extract upper 128 bits of result
__m128d sum_high = _mm256_extractf128_pd(sum1, 1);
// add upper 128 bits of sum to its lower 128 bits
__m128d result = _mm_add_pd(sum_high, _mm256_castpd256_pd128(sum));
// lower 64 bits of result contain the sum of x1[0], x1[1], x1[2], x1[3]
// upper 64 bits of result contain the sum of x2[0], x2[1], x2[2], x2[3]

Таким образом, похоже, что 3 инструкции сделают 2 из необходимых вам горизонтальных сумм. Вышеупомянутое не проверено, но вы должны понять концепцию.

person Jason R    schedule 19.03.2012
comment
Вместо использования приведения с (__m128d) я думаю, вам следует использовать _mm256_castpd256_pd128 (sum). - person Z boson; 05.12.2013
comment
Какой компилятор вы использовали для этого? Я задал вопрос о вашем составе здесь c-style-cast-versus-intrinsic-cast < / а>. У меня нет доступа к системе, чтобы проверить это прямо сейчас. - person Z boson; 05.12.2013
comment
@Zboson: Я отредактировал фрагмент, чтобы использовать более стандартный метод приведения. - person Jason R; 12.02.2014
comment
Это менее эффективно, чем возможно для процессоров Intel, и очень неэффективно для процессоров AMD. См. Получение суммы значений, хранящихся в __m256d, с помощью SSE / AVX. Это не неправильно, но вопрос задан для самого быстрого, а не простого или компактного / небольшого размера кода. - person Peter Cordes; 29.01.2019
comment
Думаю, я неправильно это понял раньше. Вы делаете 2 отдельные горизонтальные суммы. Это действительно хорошо для Intel и, возможно, нормально для Zen или Zen2. Но не в том случае, если вы в конечном итоге собираетесь сложить две половинки __m128d result друг с другом. Итак, чтобы эффективно использовать это, вам нужно иметь две независимые вещи, которые вам нужны. - person Peter Cordes; 31.03.2020

Если вам нужна только сумма и немного скалярного кода приемлемо:

__m256d x;
__m256d s = _mm256_hadd_pd(x,x);
return ((double*)&s)[0] + ((double*)&s)[2];
person RJVB    schedule 05.12.2013
comment
Пахнет неопределенным поведением с этим актерским составом. - person wingerse; 22.10.2018

Предполагая следующее, что у вас есть вектор __m256d, содержащий 4 упакованных двойника, и вы хотите вычислить сумму его компонентов, то есть a0, a1, a2, a3 - это каждый двойной компонент, который вы хотели бы a0 + a1 + a2 + a3, тогда есть другое решение AVX:

// goal to calculate a0 + a1 + a2 + a3
__m256d values = _mm256_set_pd(23211.24, -123.421, 1224.123, 413.231);

// assuming _mm256_hadd_pd(a, b) == a0 + a1, b0 + b1, a2 + a3, b2 + b3 (5 cycles) ...
values = _mm256_hadd_pd(values, _mm256_permute2f128_pd(values, values, 1));
// ^^^^^^^^^^^^^^^^^^^^ a0 + a1, a2 + a3, a2 + a3, a0 + a1

values = _mm256_hadd_pd(values, values);
// ^^^^^^^^^^^^^^^^^^^^ (a0 + a1 + a2 + a3), (a0 + a1 + a2 + a3), (a2 + a3 + a0 + a1), (a2 + a3 + a0 + a1)

// Being that addition is associative then each component of values contains the sum of all its initial components (11 cycles) to calculate, (1-2 cycles) to extract, total (12-13 cycles)
double got = _mm_cvtsd_f64(_mm256_castpd256_pd128(values)), exp = (23211.24 + -123.421 + 1224.123 + 413.231);

if (got != exp || _mm256_movemask_pd(_mm256_cmp_pd(values, _mm256_set1_pd(exp), _CMP_EQ_OS)) != 0b1111)
    printf("Failed to sum double components, exp: %f, got %f\n", exp, got);
else
    printf("ok\n");

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

Если я неверно истолковал проблему, прошу прощения.

$ uname -a
Darwin Samys-MacBook-Pro.local 13.3.0 Darwin Kernel Version 13.3.0: Tue Jun  3 21:27:35 PDT 2014; root:xnu-2422.110.17~1/RELEASE_X86_64 x86_64

$ gcc --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/usr/include/c++/4.2.1
Apple LLVM version 5.1 (clang-503.0.40) (based on LLVM 3.4svn)
Target: x86_64-apple-darwin13.3.0
Thread model: posix
person Samy Vilar    schedule 14.08.2014
comment
Если вам нужна сумма результатов для всех элементов, хадд по-прежнему не самый эффективный способ. Вам понадобится только 1 перемешивание + добавление, а не 2-кратное перемешивание мопов HADD, которые загружают одно добавление. например val = add(val, permute2f128(val,val 1)); затем поменяйте местами 128-битные дорожки с _mm256_shuffle_pd, чтобы загрузить другое добавление. На Intel это всего 4 мупа, то же самое, что и Получить сумму значений, хранящихся в __m256d, с помощью SSE / AVX - person Peter Cordes; 29.01.2019