Как суммировать __m256 по горизонтали?

Я хотел бы горизонтально суммировать компоненты вектора __m256, используя инструкции AVX. В SSE я мог бы использовать

_mm_hadd_ps(xmm,xmm);
_mm_hadd_ps(xmm,xmm);

чтобы получить результат в первом компоненте вектора, но это не масштабируется с 256-битной версией функции (_mm256_hadd_ps).

Как лучше всего вычислить горизонтальную сумму вектора __m256?


person Yoav    schedule 04.11.2012    source источник
comment
используйте sse для вычисления горизонтальной суммы нижней части; перемешайте старшие/младшие части YMM, снова используйте sse и суммируйте два скаляра. или ждать avx2.   -  person Aki Suihkonen    schedule 04.11.2012
comment
Это внутри цикла или это просто одноразовая операция?   -  person Paul R    schedule 04.11.2012
comment
Это внутри внешнего цикла, где есть другой внутренний цикл.   -  person Yoav    schedule 04.11.2012
comment
См. также этот ответ 128b SSE для получения более оптимальных (меньшая задержка, меньше операций) альтернатив haddps после того, как вы выполнили vextractf128 / addps шаг.   -  person Peter Cordes    schedule 17.02.2016


Ответы (2)


Эта версия должна быть оптимальной как для процессоров Intel Sandy/Ivy Bridge и AMD Bulldozer, так и для более поздних версий.

// x = ( x7, x6, x5, x4, x3, x2, x1, x0 )
float sum8(__m256 x) {
    // hiQuad = ( x7, x6, x5, x4 )
    const __m128 hiQuad = _mm256_extractf128_ps(x, 1);
    // loQuad = ( x3, x2, x1, x0 )
    const __m128 loQuad = _mm256_castps256_ps128(x);
    // sumQuad = ( x3 + x7, x2 + x6, x1 + x5, x0 + x4 )
    const __m128 sumQuad = _mm_add_ps(loQuad, hiQuad);
    // loDual = ( -, -, x1 + x5, x0 + x4 )
    const __m128 loDual = sumQuad;
    // hiDual = ( -, -, x3 + x7, x2 + x6 )
    const __m128 hiDual = _mm_movehl_ps(sumQuad, sumQuad);
    // sumDual = ( -, -, x1 + x3 + x5 + x7, x0 + x2 + x4 + x6 )
    const __m128 sumDual = _mm_add_ps(loDual, hiDual);
    // lo = ( -, -, -, x0 + x2 + x4 + x6 )
    const __m128 lo = sumDual;
    // hi = ( -, -, -, x1 + x3 + x5 + x7 )
    const __m128 hi = _mm_shuffle_ps(sumDual, sumDual, 0x1);
    // sum = ( -, -, -, x0 + x1 + x2 + x3 + x4 + x5 + x6 + x7 )
    const __m128 sum = _mm_add_ss(lo, hi);
    return _mm_cvtss_f32(sum);
}

haddps неэффективен ни на одном процессоре; лучшее, что вы можете сделать, это один раз перетасовать (чтобы извлечь старшую половину) и одно добавить, повторять, пока не останется один элемент. Сужение до 128-бит в качестве первого шага приносит пользу AMD перед Zen2, и нигде это не является чем-то плохим.

См. Самый быстрый способ выполнить горизонтальную векторную сумму SSE на x86 для более подробной информации об эффективности.

person Marat Dukhan    schedule 04.11.2012
comment
Есть некоторые странные угловые случаи (когда производительность ограничена декодированием), где использование haddps вместо этого дало бы преимущество, но в целом это очень разумно. - person Stephen Canon; 05.11.2012
comment
На Bulldozer hasdps имеет микрокод. Более того, он сгенерирует 3 макрооперации, в то время как код выше использует только 2 для частичной редукции. - person Marat Dukhan; 05.11.2012
comment
именно поэтому я сказал о странных угловых случаях (они очень редки и действительно странны). - person Stephen Canon; 05.11.2012
comment
Разве использование инструкций SSE (например, _mm_movehl_ps) с 256-битными инструкциями AVX не влечет за собой штраф за изменение состояния? - person timbo; 15.11.2015
comment
Инструкции SSE вызывают штраф за изменение состояния, но если вы скомпилируете наборы инструкций AVX, _mm_movehl_ps и им подобные будут генерировать AVX-формы инструкций (VMOVHLPS в данном конкретном случае). - person Marat Dukhan; 15.11.2015

Это можно сделать с помощью следующего кода:

ymm2 = _mm256_permute2f128_ps(ymm , ymm , 1);
ymm = _mm256_add_ps(ymm, ymm2);
ymm = _mm256_hadd_ps(ymm, ymm);
ymm = _mm256_hadd_ps(ymm, ymm);

но может быть лучшее решение.

person Yoav    schedule 04.11.2012
comment
Я заметил, что перестановка + добавление также может идти после двух хаддов. - person user2023370; 05.04.2019