我想我最初可能会选择“蛮力和无知”的方法,可能是这样的:
uint8_t u = 0x53; // 01010011
const union {
uint32_t a[4];
__m128i v;
} kLUT[16] = { { { 0, 0, 0, 0 } },
{ { -1, 0, 0, 0 } },
{ { 0, -1, 0, 0 } },
{ { -1, -1, 0, 0 } },
{ { 0, 0, -1, 0 } },
{ { -1, 0, -1, 0 } },
{ { 0, -1, -1, 0 } },
{ { -1, -1, -1, 0 } },
{ { 0, 0, 0, -1 } },
{ { -1, 0, 0, -1 } },
{ { 0, -1, 0, -1 } },
{ { -1, -1, 0, -1 } },
{ { 0, 0, -1, -1 } },
{ { -1, 0, -1, -1 } },
{ { 0, -1, -1, -1 } },
{ { -1, -1, -1, -1 } } };
__m256i v = _mm256_set_m128i(kLUT[u >> 4].v, kLUT[u & 15].v);
使用clang -O3 编译为:
movl %ebx, %eax ;; eax = ebx = u
andl $15, %eax ;; get low offset = (u & 15) * 16
shlq $4, %rax
leaq _main.kLUT(%rip), %rcx ;; rcx = kLUT
vmovaps (%rax,%rcx), %xmm0 ;; load low half of ymm0 from kLUT
andl $240, %ebx ;; get high offset = (u >> 4) * 16
vinsertf128 $1, (%rbx,%rcx), %ymm0, %ymm0
;; load high half of ymm0 from kLUT
FWIW 我为三个实现组合了一个简单的测试工具:(i) 一个简单的标量代码参考实现,(ii) 上述代码,(iii) 基于@Zboson 答案的实现,(iv) 略微改进的版本(iii)和(v)使用@MarcGlisse的建议对(iv)的进一步改进。我使用 2.6GHz Haswell CPU(使用 clang -O3 编译)得到以下结果:
scalar code: 7.55336 ns / vector
Paul R: 1.36016 ns / vector
Z boson: 1.24863 ns / vector
Z boson (improved): 1.07590 ns / vector
Z boson (improved + @MarcGlisse suggestion): 1.08195 ns / vector
所以@Zboson 的解决方案赢了大约 10% - 20%,大概是因为他们只需要 1 个负载,而我的需要 2 个。
如果我们得到任何其他实现,我会将它们添加到测试工具中并更新结果。
@Zboson 实现的略微改进版本:
__m256i v = _mm256_set1_epi8(u);
v = _mm256_and_si256(v, mask);
v = _mm256_xor_si256(v, mask);
return _mm256_cmpeq_epi32(v, _mm256_setzero_si256());
@Zboson 实施的进一步改进版本,结合了@MarcGlisse 的建议:
__m256i v = _mm256_set1_epi8(u);
v = _mm256_and_si256(v, mask);
return _mm256_cmpeq_epi32(v, mask);
(注意mask需要在每个32位元素中包含复制的8位值,即0x01010101, 0x02020202, ..., 0x80808080)