Суммирование 8-разрядных целых чисел в __m512i с помощью встроенных функций AVX

#c #x86 #simd #intrinsics #avx

#c #x86 #simd #встроенные функции #avx

Вопрос:

AVX512 предоставляет нам встроенные функции для суммирования всех ячеек в __mm512 векторе. Однако некоторые из их аналогов отсутствуют: пока нет _mm512_reduce_add_epi8 .

 _mm512_reduce_add_ps     //horizontal sum of 16 floats
_mm512_reduce_add_pd     //horizontal sum of 8 doubles
_mm512_reduce_add_epi32  //horizontal sum of 16 32-bit integers
_mm512_reduce_add_epi64  //horizontal sum of 8 64-bit integers
  

В принципе, мне нужно реализовать MAGIC в следующем фрагменте.

 __m512i all_ones = _mm512_set1_epi16(1);
short sum_of_ones = MAGIC(all_ones);
/* now sum_of_ones contains 32, the sum of 32 ones. */
  

Наиболее очевидным способом было бы использовать _mm512_storeu_epi8 и суммировать элементы массива вместе, но это было бы медленно, плюс это могло бы привести к недействительности кэша. Я полагаю, что существует более быстрый подход.

Также бонусные баллы за реализацию _mm512_reduce_add_epi16 .

Комментарии:

1. Поскольку вы наивно упомянули, что это «медленно», это подразумевает, что это критически важно для производительности. Каково здесь использование? Если это большая операция сокращения, есть способы сделать это получше.

Ответ №1:

Прежде всего, _mm512_reduce_add_epi64 не соответствует ни одной инструкции AVX512, но генерирует последовательность перетасовок и дополнений.

Для уменьшения 64 epu8 значений до 8 epi64 значений обычно используется vpsadbw инструкция (SAD = Сумма абсолютных разностей) по отношению к нулевому вектору, который затем может быть дополнительно уменьшен:

 long reduce_add_epu8(__m512i a)
{
    return _mm512_reduce_add_epi64(_mm512_sad_epu8(a, _mm512_setzero_si512()));
}
  

Попробуйте это на godbolt: https://godbolt.org/z/1rMiPH . К сожалению, ни GCC, ни Clang, похоже, не способны оптимизировать функцию, если она используется с _mm512_set1_epi16(1) .

Для epi8 вместо epu8 0x80 вам нужно сначала добавить 128 к каждому элементу (или xor с помощью vpsadbw ), затем уменьшить его с помощью 64*128 и в конце вычесть 8*128 (или, в конце концов, для каждого промежуточного 64-битного результата). [Обратите внимание, что это было неправильно в предыдущей версии этого ответа]

Ибо epi16 я предлагаю взглянуть на то, какие инструкции _mm512_reduce_add_epi32 и _mm512_reduce_add_epi64 сгенерировать и вывести оттуда, что делать.


В целом, как предположил @Mysticial, наилучший подход к сокращению зависит от вашего контекста. Например, если у вас очень большой массив int64 и вам нужна сумма в виде int64 , вы должны просто сложить их вместе по пакетам и только в самом конце свести один пакет к одному int64 .