我如何用 AVX512 本征收集单字节,给定一个 int 偏移量的向量?

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

我有一个基本地址(uint8_t*)和一个16个偏移量的向量(__m512i).我需要最终得到一个__m128i,包含从16个不同的内存位置收集的16个字节。

就目前而言,我了解到没有这样的基元,我所能使用的是

uint8_t base;
__m512i offsets;
__m512i values = _mm512_i32gather_epi32(base, offsets, 1);

这给了我一个__m512i,在那里我有

Vjjj Vjjj Vjjj Vjjj Vjjj Vjjj Vjjj Vjjj Vjjj Vjjj Vjjj Vjjj Vjjj Vjjj Vjjj Vjjj

(j是垃圾,V是我感兴趣的值)

现在我需要重新打包数据,以便最终得到一个只包含我感兴趣的数据的向量,但我越来越困惑,我甚至不知道我的方法是否正确。

c sse simd intrinsics avx512
1个回答
2
投票

你要找的shuffle是AVX512F的一部分。_mm512_cvtepi32_epi8 (VPMOVDB). 有趣的事实是:如果你想要的话,它甚至有一个内存-目的地存储形式,尽管在Skylake-avx512硬件上,这并不比正常的效率高。 (在没有AVX512BW的Xeon Phi上,它确实允许字节屏蔽存储)。

是的,如果你能安全地在每个字节元素的末尾读取3个字节的垃圾的话 而不会因触及未映射的页面而产生错误的风险。, 词条收集+包装可能是你最好的选择。. 特别是当它们不太可能跨越缓存线或特别是页面边界时。 如果你的索引中偏向于那些最坏的情况下的字节位置,可以考虑以不同的方式对齐你的源数据,或者做一些其他的事情。

如果索引中有任何一种模式,那可以使手动加载+洗牌更有效率,特别是当一个向量加载可以跨越多个你想要的值时。即使只是有固定的步长,也可以考虑在索引上循环,一次插入一个元素,用 vpinsrb 诸如此类 AVX2 字节集合,用 uint16 索引,变成一个 __m256i。. 但是用最近的硬件(Skylake)和宽向量(特别是AVX512),采集效果相当好,每时钟可以接近0.5个元素。


你把操作数的顺序弄错了 _mm512_i32gather_epi32base 当然需要是一个指针,而不是标量。uin8_t:

__m128i bytegather(uint8_t *base, __m512i offsets)
{
    __m512i values = _mm512_i32gather_epi32(offsets, base, 1);
    return _mm512_cvtepi32_epi8(values);   // pack with truncation.
}

对于AVX2版本,有: _mm256_i32gather_epi32,你必须使用不同的洗牌。 也许提取高半部分,左移,字混合(vpblendw),所以所有你想要的字节都在一个 __m128i. 那么 vpshufb (_mm_shuffle_epi8)来把你想要的8个字节打包到寄存器的底部?

从高半部分的指数中减去一个或两个,在gather之前可以避免需要移位,所以你想要的字节在dword元素的不同位置。 但请注意,这意味着如果 index=0 你从表的开始之前加载。 所以你不能这样做,如果这可能会导致segfault。 而且这对性能来说可能是个坏主意)。


如果你有多个这样的向量,并且想最终建立一个 __m512i 4个偏移量向量的字节数。,您可以考虑使用2个输入包指令(如 _mm512_packs_epi32 vpackssdw)与最终的qword shuffle来修正行内行为。 但是那些包只有饱和版本,没有截断版本,而且要先清除每个输入的高垃圾,会花费额外的指令。

相反也许最好使用 _mm512_permutex2var_epi16 (vpermt2w)的第一步,虽然在Skylake-X上要花费多次洗牌uops,不幸的是在冰湖上也要花费多次洗牌uops,在那里 vpermb 是单uop。 你会想计算总的洗牌uops来产生一个 __m512i 从4 __m512i 输入,看看哪一个最便宜,对于这一点与截断至 __m128i_mm512_cvtepi32_epi8 然后再建立起来。

© www.soinside.com 2019 - 2024. All rights reserved.