Самый эффективный способ получить __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 результат на самом деле используется.

Другие вопросы по тегам