我有一个基本地址(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是我感兴趣的值)
现在我需要重新打包数据,以便最终得到一个只包含我感兴趣的数据的向量,但我越来越困惑,我甚至不知道我的方法是否正确。
你要找的shuffle是AVX512F的一部分。_mm512_cvtepi32_epi8
(VPMOVDB
). 有趣的事实是:如果你想要的话,它甚至有一个内存-目的地存储形式,尽管在Skylake-avx512硬件上,这并不比正常的效率高。 (在没有AVX512BW的Xeon Phi上,它确实允许字节屏蔽存储)。
是的,如果你能安全地在每个字节元素的末尾读取3个字节的垃圾的话 而不会因触及未映射的页面而产生错误的风险。, 词条收集+包装可能是你最好的选择。. 特别是当它们不太可能跨越缓存线或特别是页面边界时。 如果你的索引中偏向于那些最坏的情况下的字节位置,可以考虑以不同的方式对齐你的源数据,或者做一些其他的事情。
如果索引中有任何一种模式,那可以使手动加载+洗牌更有效率,特别是当一个向量加载可以跨越多个你想要的值时。即使只是有固定的步长,也可以考虑在索引上循环,一次插入一个元素,用 vpinsrb
诸如此类 AVX2 字节集合,用 uint16 索引,变成一个 __m256i。. 但是用最近的硬件(Skylake)和宽向量(特别是AVX512),采集效果相当好,每时钟可以接近0.5个元素。
你把操作数的顺序弄错了 _mm512_i32gather_epi32
和 base
当然需要是一个指针,而不是标量。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
然后再建立起来。