使用AVX2是否可以实现对字数组上LZCNT的更快处理?

问题描述 投票:0回答:2

我需要使用 LZCNT 进行反向位扫描,字数组:16 位。

LZCNT 的吞吐量是 Intel 最新一代处理器上每个时钟执行 1 次。 AMD Ryzen 上的吞吐量似乎是 4。

我正在尝试寻找一种使用 AVX2 指令集更快的算法。

我知道 AVX-512 有 VPLZCNTD 用于 32 位元素,所以如果我有 AVX512CD,我可以解压并使用它。

仅使用 AVX2 指令集,就可以比使用 x86 asm LZCNT 指令更快地编写算法吗?

x86 simd avx micro-optimization avx2
2个回答
8
投票
#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 的解决方案。 此解决方案也在此处的评论中进行了描述。


0
投票

另一个可能的解决方案是使用转换为浮点技巧,超越这个答案。在我对 Zen4 的测试中,性能稍好一些,并且使用的寄存器更少。

__m256i avx2_bit_width_epu16(__m256i v)
{
    const __m256i mask = _mm256_set1_epi32(0x0000FFFF);
    __m256i t = _mm256_and_si256(mask, v); // even indices
    v = _mm256_andnot_si256(mask, v); // odd indices - this prevents rounding

    t = _mm256_castps_si256(_mm256_cvtepi32_ps(t));
    v = _mm256_castps_si256(_mm256_cvtepi32_ps(v)); // convert integers to floats

    t = _mm256_alignr_epi8(t, t, 2); // put exponents inplace
    v = _mm256_blend_epi16(t, v, 0b10101010); // restore

    v = _mm256_srli_epi16(v, 23 - 16); // shift down the exponent
    v = _mm256_sub_epi16(v, _mm256_set1_epi32(((126 + 16) << 16) + 126)); // undo bias
    v = _mm256_max_epi16(v, _mm256_set1_epi16(0)); // clamp negative for 0 to 0

    return v;
}
© www.soinside.com 2019 - 2024. All rights reserved.