Самый эффективный способ получить __m256 горизонтальных сумм из 8 исходных __m256 векторов
Я знаю как подвести итог __m256
чтобы получить единую сумму. Тем не менее, у меня есть 8 векторов, как ввод
1: a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7],
.....,
.....,
8: h[0], h[1], h[2], h[3], h[4], a[5], a[6], a[7]
Выход
a[0]+a[1]+a[2]+a[3]+a[4]+a[5]+a[6]+a[7],
....,
h[0]+h[1]+h[2]+h[3]+h[4]+h[5]+h[6]+h[7]
Мой метод. Любопытно, если есть лучший способ.
__m256 sumab = _mm256_hadd_ps(accumulator1, accumulator2);
__m256 sumcd = _mm256_hadd_ps(accumulator3, accumulator4);
__m256 sumef = _mm256_hadd_ps(accumulator5, accumulator6);
__m256 sumgh = _mm256_hadd_ps(accumulator7, accumulator8);
__m256 sumabcd = _mm256_hadd_ps(sumab, sumcd);
__m256 sumefgh = _mm256_hadd_ps(sumef, sumgh);
__m128 sumabcd1 = _mm256_extractf128_ps(sumabcd, 0);
__m128 sumabcd2 = _mm256_extractf128_ps(sumabcd, 1);
__m128 sumefgh1 = _mm256_extractf128_ps(sumefgh, 0);
__m128 sumefgh2 = _mm256_extractf128_ps(sumefgh, 1);
sumabcd1 = _mm_add_ps(sumabcd1, sumabcd2);
sumefgh1 = _mm_add_ps(sumefgh1, sumefgh2);
__m256 result =_mm256_insertf128_ps(_mm256_castps128_ps256(sumabcd1), sumefgh1, 1)
1 ответ
Вы можете использовать 2x _mm256_permute2f128_ps
выровнять низкие и высокие полосы по вертикали vaddps
, Это вместо 2х extractf128
/ insertf128
, Это также превращает два 128b vaddps xmm
инструкции в одном 256b vaddps ymm
,
vperm2f128
так же быстро, как один vextractf128
или же vinsertf128
на процессорах Intel. Тем не менее, он медленен для AMD (8 мегапикселей с задержкой 4c в семействе Bulldozer). Тем не менее, не все так плохо, что вам нужно избегать этого, даже если вы заботитесь о производительности на AMD. (И один из пермутатов на самом деле может быть vinsertf128
).
__m256 hsum8(__m256 a, __m256 b, __m256 c, __m256 d,
__m256 e, __m256 f, __m256 g, __m256 h)
{
// a = [ A7 A6 A5 A4 | A3 A2 A1 A0 ]
__m256 sumab = _mm256_hadd_ps(a, b);
__m256 sumcd = _mm256_hadd_ps(c, d);
__m256 sumef = _mm256_hadd_ps(e, f);
__m256 sumgh = _mm256_hadd_ps(g, h);
__m256 sumabcd = _mm256_hadd_ps(sumab, sumcd); // [ D7:4 ... A7:4 | D3:0 ... A3:0 ]
__m256 sumefgh = _mm256_hadd_ps(sumef, sumgh); // [ H7:4 ... E7:4 | H3:0 ... E3:0 ]
__m256 sum_hi = _mm256_permute2f128_ps(sumabcd, sumefgh, 0x31); // [ H7:4 ... E7:4 | D7:4 ... A7:4 ]
__m256 sum_lo = _mm256_permute2f128_ps(sumabcd, sumefgh, 0x20); // [ H3:0 ... E3:0 | D3:0 ... A3:0 ]
__m256 result = _mm256_add_ps(sum_hi, sum_lo);
return result;
}
Это компилируется, как вы ожидаете. Второй permute2f128
на самом деле компилируется в vinsertf128
, поскольку он использует нижнюю полосу каждого входа так же, как vinsertf128
делает. gcc 4.7 и более поздние версии выполняют эту оптимизацию, но только в более поздних версиях clang (v3.7). Если вы заботитесь о старом лязге, делайте это на уровне источника.
Экономия в исходных строках больше, чем в инструкциях, потому что _mm256_extractf128_ps(sumabcd, 0);
компилирует до нуля инструкции: это просто приведение. Ни один компилятор никогда не должен испускать vextractf128
с imm8 кроме 1
, (vmovdqa xmm/m128, xmm
всегда лучше для получения низкой полосы движения). Хорошая работа Intel - тратить инструктивный байт на защиту будущего, который вы не можете использовать, потому что у простых префиксов VEX нет места для кодирования более длинных векторов.
Два vaddps xmm
инструкции могут выполняться параллельно, поэтому с помощью одного vaddps ymm
в основном это просто пропускная способность (и размер кода), а не задержка.
Мы сбриваем 3 цикла от полного устранения финала vinsertf128
, хоть.
vhaddps
равен 3 моп, задержка 5 с и пропускная способность по одному на 2 с. (Задержка 6c на Skylake). Два из этих трех мопов работают в порту случайного воспроизведения. Я думаю, это в основном делает 2x shufps
генерировать операнды для addps
,
Если мы можем подражать haddps
(или, по крайней мере, получить горизонтальную операцию, которую мы можем использовать) с одним shufps
/ addps
или что-то, мы вышли бы вперед. К сожалению, я не вижу как. Один случайный случай может дать только один результат с данными из двух векторов, но нам нужны оба входа для вертикального addps
иметь данные из обоих векторов.
Я не думаю, что делать горизонтальную сумму по-другому выглядит многообещающе. Обычно, HADD не является хорошим выбором, потому что общий вариант использования с горизонтальной суммой заботится только об одном элементе его вывода. Это не тот случай: каждый элемент каждого hadd
результат на самом деле используется.