我有一个 avx2(256 位)SIMD 字节向量,前面和后面都用零填充,如下所示:
[0, 2, 3, ..., 4, 5, 0, 0, 0]
。
前面的零的数量在编译时是未知的。
我如何有效地移动/旋转零点,使其看起来像这样:
[2, 3, 4, 5, ..., 0, 0, 0, 0]
?
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 即可获得负载偏移量。
_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)