(Связано: если вы ищете несуществующий _mm512_reduce_add_epu8
, см. Суммирование 8-битных целых чисел в __m512i с внутренними AVX ; vpsadbw
в качестве значения hsum в qwords гораздо эффективнее чем перетасовка.)
В immintrin.h
есть встроенная функция int _mm512_reduce_add_epi32(__m512i)
. Вы могли бы также использовать это. (Он компилируется для перемешивания и добавления инструкций, но более эффективен, чем vpermd
, как я опишу ниже.) AVX512 не представил никакой новой аппаратной поддержки для горизонтальных сумм, только этот новый помощник функция. Это все еще что-то, чтобы избежать когда бы ни было возможно.
G CC 9.2 -O3 -march=skylake-avx512
компилирует обертку, которая вызывает это следующим образом:
vextracti64x4 ymm1, zmm0, 0x1
vpaddd ymm1, ymm1, ymm0
vextracti64x2 xmm0, ymm1, 0x1 # silly compiler, vextracti128 would be shorter
vpaddd xmm1, xmm0, xmm1
vpshufd xmm0, xmm1, 78
vpaddd xmm0, xmm0, xmm1
vmovd edx, xmm0
vpextrd eax, xmm0, 1 # 2x xmm->integer to feed scalar add.
add eax, edx
ret
Извлечение дважды для подачи скаляра добавить сомнительно; для p0 и p5 нужны мопы, поэтому это эквивалентно обычному перемешиванию + a movd
.
Clang этого не делает; он делает еще один шаг добавления shuffle / SIMD, чтобы уменьшить до одного скаляра для vmovd
. Ниже приведен анализ этих двух параметров.
Существует VPHADDD
, но вы никогда не должны использовать его с обоими входами одинаково. (Если вы не оптимизируете размер кода по скорости). Может быть полезно транспонировать и суммировать несколько векторов, что приводит к некоторым векторам результатов. Вы делаете это путем подачи phadd
2 различных входов. (За исключением того, что он запутывается с 256 и 512 битами, потому что vphadd
по-прежнему только на линии.)
Да, вам нужны log2(vector_width)
шаффлы и vpaddd
инструкции. ( Так что это не очень эффективно, избегайте горизонтальных сумм внутри внутренних циклов. Накапливайте по вертикали до конца al oop, например).
Вы хотите последовательно сузить от 512 -> 256, затем 256 -> 128, затем перетасуйте в пределах __m128i
, пока не получите один скалярный элемент . Предположительно некоторые будущие процессоры AMD будут декодировать 512-битные инструкции в два 256-битных мопа, поэтому уменьшение ширины является большой победой. Более узкие инструкции, вероятно, стоят немного меньше энергии.
Ваши тасовки могут принимать непосредственные управляющие операнды, а не векторы для vpermd
. , например, VEXTRACTI32x8
, vextracti128
и vpshufd
. (Или vpunpckhqdq
, чтобы сохранить размер кода для непосредственной константы.)
См. Самый быстрый способ сделать горизонтальную векторную сумму с плавающей запятой на x86 (мой ответ также включает несколько целочисленных версий).
Эта общая стратегия подходит для всех типов элементов: с плавающей запятой, двойных чисел и целых чисел любого размера (кроме 8-битных, где вы используете vpsadbw
)
#include <immintrin.h>
#include <stdint.h>
// from my earlier answer, with tuning for non-AVX CPUs removed
// static inline
uint32_t hsum_epi32_avx(__m128i x)
{
__m128i hi64 = _mm_unpackhi_epi64(x, x); // 3-operand non-destructive AVX lets us save a byte without needing a movdqa
__m128i sum64 = _mm_add_epi32(hi64, x);
__m128i hi32 = _mm_shuffle_epi32(sum64, _MM_SHUFFLE(2, 3, 0, 1)); // Swap the low two elements
__m128i sum32 = _mm_add_epi32(sum64, hi32);
return _mm_cvtsi128_si32(sum32); // movd
}
uint32_t hsum_8x32(__m256i v)
{
__m128i sum128 = _mm_add_epi32(
_mm256_castsi256_si128(v),
_mm256_extracti128_si256(v, 1)); // silly GCC uses a longer AXV512VL instruction :/
return hsum_epi32_avx(sum128);
}
uint32_t hsum_16x32(__m512i v)
{
__m256i sum256 = _mm256_add_epi32(
_mm512_castsi512_si256(v), // low half
_mm512_extracti64x4_epi64(v, 1)); // high half. AVX512F. 32x8 version is AVX512DQ
return hsum_8x32(sum256);
}
Обратите внимание, что здесь используется __m256i
хсум как строительный блок для __m512i
; вначале ничего нельзя получить, выполняя операции в полосе.
Возможно, это очень маленькое преимущество: задержки в полосе имеют меньшую задержку, чем при пересечении полосы, поэтому они могут выполнить на 2 цикла раньше и покинуть RS раньше. и аналогичным образом удалиться из ROB немного раньше. Но тасовки с более высокой задержкой появятся через пару инструкций, даже если вы это сделали. Таким образом, вы могли бы получить несколько независимых инструкций для серверных 2-х циклов ранее, если этот hsum находился на критическом пути (блокирование выхода на пенсию).
Но сокращение до более узкой векторной ширины раньше, как правило, хорошо, возможно получение 512-битных мопов из системы быстрее, чтобы процессор мог повторно активировать исполнительные блоки SIMD на порту 1, если вы не выполняете больше 512-битной работы прямо сейчас.
Компилирует вкл Godbolt к этим инструкциям, с GCC9.2 -O3 -march=skylake-avx512
hsum_16x32(long long __vector(8)):
vextracti64x4 ymm1, zmm0, 0x1
vpaddd ymm0, ymm1, ymm0
vextracti64x2 xmm1, ymm0, 0x1 # silly compiler uses a longer EVEX instruction when its available (AVX512VL)
vpaddd xmm0, xmm0, xmm1
vpunpckhqdq xmm1, xmm0, xmm0
vpaddd xmm0, xmm0, xmm1
vpshufd xmm1, xmm0, 177
vpaddd xmm0, xmm1, xmm0
vmovd eax, xmm0
ret
PS: анализ перфомента G CC '_mm512_reduce_add_epi32
против Clang's (что эквивалентно моей версии), используя данные из https://uops.info/ и / или таблиц инструкций Agner Fog :
После встраивания в вызывающую программу, которая что-то делает с В результате, это может позволить оптимизацию, такую как добавление константы, используя lea eax, [rax + rdx + 123]
или что-то в этом роде.
Но в остальном он кажется почти всегда хуже, чем shuffle / vpadd / vmovd в конце моей реализации, на скайле ke-X:
- всего моп: уменьшить: 4. Шахта: 3
- порты: уменьшить: 2p0, p5 (часть vpextrd), p0156 (скаляр
add
) - порты: мой: p5, p015 (
vpadd
на SKX), p0 (vmod
)
Задержка равна 4 циклам, при условии отсутствия конфликтов ресурсов:
- перемешать 1 цикл -> SIMD добавить 1 цикл -> vmovd 2 цикла
- vpextrd 3 цикла (параллельно с 2 циклами vmovd) -> добавить 1 цикл.