使用 SIMD 移动/旋转字节向量的最快方法

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

我有一个 avx2(256 位)SIMD 字节向量,前面和后面都用零填充,如下所示:

[0, 2, 3, ..., 4, 5, 0, 0, 0]
。 前面的零的数量在编译时是未知的。

我如何有效地移动/旋转零点,使其看起来像这样:

[2, 3, 4, 5, ..., 0, 0, 0, 0]

c++ assembly simd avx avx2
3个回答
2
投票

AVX2 无法进行粒度小于 4 字节的跨车道洗牌。在这种情况下,您需要 AVX-512 VBMI

vpermb
(在 Ice Lake 中)。如果你有这个,也许在掩码上使用
vpcmpeqb
/
vpmovmskb
/
tzcnt
,并使用它作为偏移量从
alignas(64) int8_t shuffles = {0,1,2,...,31, 0, 1, 2, ... 31};
常量数组加载 32 字节的窗口。这就是
vpermb
的随机播放控制向量。


如果没有 AVX-512 VBMI,尽管存在存储转发停滞,但存储两次并跨它们进行未对齐的重新加载可能是有意义的。如果您在许多其他工作之间需要一个向量,那么这对吞吐量来说是有好处的,但对于在没有太多其他工作的循环中执行此操作不利。

存储转发停顿不会相互管道化,但可以通过成功的存储转发进行管道化

。因此,如果您只是偶尔需要一个向量,并且无序执行可以隐藏延迟,则 vpcmpeqb/tzcnt 或 lzcnt 不需要太多 uops 即可获得负载偏移量。


2
投票

我不太理解

_mm256_permutevar8x32_epi32

的文档,但在实践中,向身份排列添加偏移量会进行旋转 - 这就是你想要的(当你已经获得前导 0 的数量时)。

__m256i rotate_i32(__m256i w, int offset) {
    __m256i identity = _mm256_set_epi32(7, 6, 5, 4, 3, 2, 1, 0);
    __m256i shuffle = _mm256_add_epi32(identity, _mm256_set1_epi32(offset));
    return _mm256_permutevar8x32_epi32(w, shuffle);
}

这是上帝螺栓:
https://godbolt.org/z/Kv8oxs6oY

(-1, -2, -3, -4, -5, -6, -7, -8) (-2, -3, -4, -5, -6, -7, -8, -1) (-3, -4, -5, -6, -7, -8, -1, -2) (-4, -5, -6, -7, -8, -1, -2, -3) (-5, -6, -7, -8, -1, -2, -3, -4) (-6, -7, -8, -1, -2, -3, -4, -5) (-7, -8, -1, -2, -3, -4, -5, -6) (-8, -1, -2, -3, -4, -5, -6, -7)

同样的技巧适用于 64 位,但您需要乘以 2 的偏移量。

__m256i rotate_i64(__m256i w, int offset) { __m256i identity = _mm256_set_epi32(7, 6, 5, 4, 3, 2, 1, 0); __m256i shuffle = _mm256_add_epi32(identity, _mm256_set1_epi32(offset * 2)); return _mm256_permutevar8x32_epi32(w, shuffle); }

上帝螺栓:
https://godbolt.org/z/85h6aWPsW

输出:

(-1, -2, -3, -4) (-2, -3, -4, -1) (-3, -4, -1, -2) (-4, -1, -2, -3)



0
投票
© www.soinside.com 2019 - 2024. All rights reserved.