使用 AVX2 是否可以在字数组上实现更快的 LZCNT 处理?
Is using AVX2 can implement a faster processing of LZCNT on a word array?
我需要用 LZCNT 对一个字数组进行位扫描:16 位。
在英特尔最新一代处理器上,LZCNT 的吞吐量是每个时钟执行 1 次。 AMD Ryzen 的吞吐量似乎是 4。
我正在尝试寻找一种使用 AVX2 指令集的算法更快。
我知道 AVX-512 有 VPLZCNTD 用于 32 位元素,所以如果我有 AVX512CD,我可以解压并使用它。
仅使用 AVX2 指令集,是否可以比使用 x86 asm LZCNT 指令更快地编写算法代码?
#include <immintrin.h>
__m256i avx2_lzcnt_epi16(__m256i v) {
const __m256i lut_lo = _mm256_set_epi8(
4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 16,
4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 16
);
const __m256i lut_hi = _mm256_set_epi8(
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 16,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 16
);
const __m256i nibble_mask = _mm256_set1_epi8(0x0F);
const __m256i byte_offset = _mm256_set1_epi16(0x0008);
__m256i t;
t = _mm256_and_si256(nibble_mask, v);
v = _mm256_and_si256(_mm256_srli_epi16(v, 4), nibble_mask);
t = _mm256_shuffle_epi8(lut_lo, t);
v = _mm256_shuffle_epi8(lut_hi, v);
v = _mm256_min_epu8(v, t);
t = _mm256_srli_epi16(v, 8);
v = _mm256_or_si256(v, byte_offset);
v = _mm256_min_epu8(v, t);
return v;
}
// 16 - lzcnt_u16(subwords)
__m256i avx2_ms1b_epi16(__m256i v) {
const __m256i lut_lo = _mm256_set_epi8(
12, 12, 12, 12, 12, 12, 12, 12, 11, 11, 11, 11, 10, 10, 9, 0,
12, 12, 12, 12, 12, 12, 12, 12, 11, 11, 11, 11, 10, 10, 9, 0
);
const __m256i lut_hi = _mm256_set_epi8(
16, 16, 16, 16, 16, 16, 16, 16, 15, 15, 15, 15, 14, 14, 13, 0,
16, 16, 16, 16, 16, 16, 16, 16, 15, 15, 15, 15, 14, 14, 13, 0
);
const __m256i nibble_mask = _mm256_set1_epi8(0x0F);
const __m256i adj = _mm256_set1_epi16(0x1F08);
__m256i t;
t = _mm256_and_si256(nibble_mask, v);
v = _mm256_and_si256(_mm256_srli_epi16(v, 4), nibble_mask);
t = _mm256_shuffle_epi8(lut_lo, t);
v = _mm256_shuffle_epi8(lut_hi, v);
v = _mm256_max_epu8(v, t);
t = _mm256_srli_epi16(v, 8);
v = _mm256_sub_epi8(v, adj);
v = _mm256_max_epi8(v, t);
return v;
}
对于打包到 uint8 中的结果,使用 _mm256_packs_epi16()
。
对于正确顺序的打包结果,还使用 _mm256_permute4x64_epi64()
.
来自 r/SIMD 的解决方案。
此处的评论中也描述了此解决方案。
我需要用 LZCNT 对一个字数组进行位扫描:16 位。
在英特尔最新一代处理器上,LZCNT 的吞吐量是每个时钟执行 1 次。 AMD Ryzen 的吞吐量似乎是 4。
我正在尝试寻找一种使用 AVX2 指令集的算法更快。
我知道 AVX-512 有 VPLZCNTD 用于 32 位元素,所以如果我有 AVX512CD,我可以解压并使用它。
仅使用 AVX2 指令集,是否可以比使用 x86 asm LZCNT 指令更快地编写算法代码?
#include <immintrin.h>
__m256i avx2_lzcnt_epi16(__m256i v) {
const __m256i lut_lo = _mm256_set_epi8(
4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 16,
4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 16
);
const __m256i lut_hi = _mm256_set_epi8(
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 16,
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 3, 16
);
const __m256i nibble_mask = _mm256_set1_epi8(0x0F);
const __m256i byte_offset = _mm256_set1_epi16(0x0008);
__m256i t;
t = _mm256_and_si256(nibble_mask, v);
v = _mm256_and_si256(_mm256_srli_epi16(v, 4), nibble_mask);
t = _mm256_shuffle_epi8(lut_lo, t);
v = _mm256_shuffle_epi8(lut_hi, v);
v = _mm256_min_epu8(v, t);
t = _mm256_srli_epi16(v, 8);
v = _mm256_or_si256(v, byte_offset);
v = _mm256_min_epu8(v, t);
return v;
}
// 16 - lzcnt_u16(subwords)
__m256i avx2_ms1b_epi16(__m256i v) {
const __m256i lut_lo = _mm256_set_epi8(
12, 12, 12, 12, 12, 12, 12, 12, 11, 11, 11, 11, 10, 10, 9, 0,
12, 12, 12, 12, 12, 12, 12, 12, 11, 11, 11, 11, 10, 10, 9, 0
);
const __m256i lut_hi = _mm256_set_epi8(
16, 16, 16, 16, 16, 16, 16, 16, 15, 15, 15, 15, 14, 14, 13, 0,
16, 16, 16, 16, 16, 16, 16, 16, 15, 15, 15, 15, 14, 14, 13, 0
);
const __m256i nibble_mask = _mm256_set1_epi8(0x0F);
const __m256i adj = _mm256_set1_epi16(0x1F08);
__m256i t;
t = _mm256_and_si256(nibble_mask, v);
v = _mm256_and_si256(_mm256_srli_epi16(v, 4), nibble_mask);
t = _mm256_shuffle_epi8(lut_lo, t);
v = _mm256_shuffle_epi8(lut_hi, v);
v = _mm256_max_epu8(v, t);
t = _mm256_srli_epi16(v, 8);
v = _mm256_sub_epi8(v, adj);
v = _mm256_max_epi8(v, t);
return v;
}
对于打包到 uint8 中的结果,使用 _mm256_packs_epi16()
。
对于正确顺序的打包结果,还使用 _mm256_permute4x64_epi64()
.
来自 r/SIMD 的解决方案。 此处的评论中也描述了此解决方案。