你可以使用
PSADBW
来计算字节的水平总和,而不会发生溢出。例如:
pxor xmm0, xmm0
psadbw xmm0, [a + 0]
pxor xmm1, xmm1
psadbw xmm1, [a + 16]
paddw xmm0, xmm1
pshufd xmm1, xmm0, 2
paddw xmm0, xmm1
内部版本:
#include <immintrin.h>
#include <stdint.h>
unsigned sum_32x8(const uint8_t a[32])
{
__m128i zero = _mm_setzero_si128();
__m128i sum0 = _mm_sad_epu8( zero,
_mm_load_si128(reinterpret_cast<const __m128i*>(a)));
__m128i sum1 = _mm_sad_epu8( zero,
_mm_load_si128(reinterpret_cast<const __m128i*>(&a[16])));
__m128i sum2 = _mm_add_epi32(sum0, sum1);
__m128i totalsum = _mm_add_epi32(sum2, _mm_shuffle_epi32(sum2, 2));
return _mm_cvtsi128_si32(totalsum);
}
这段文字的意思是:这个程序可以被轻松地编译成相同的汇编代码,你可以在
Godbolt上看到。
reinterpret_cast<const __m128i*>
是必要的,因为在AVX-512之前的Intel指令集中,整数向量的加载/存储需要__m128i*
指针参数,而不是更方便的void*
。有些人喜欢使用更紧凑的C风格转换,如_mm_loadu_si128((const __m128*)&a[16])
作为样式选择。
16、32和64位SIMD元素大小并不重要;在所有机器上,16和32位同样有效,并且32位将避免溢出,即使您将其用于求和更大的数组。(paddq
在一些旧CPU上(如Core 2)速度较慢;请参见https://agner.org/optimize/和https://uops.info/)。提取为32位肯定比_mm_extract_epi16
(pextrw
)更有效。