给定一个 int 偏移量向量,如何使用 AVX512 内部函数收集单个字节?

How can I gather single bytes with AVX512 intrinsics, given a vector of int offsets?

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

目前我明白没有这样的原语,能用的只有

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是我感兴趣的值)

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

您要查找的随机播放是 AVX512F 的一部分:_mm512_cvtepi32_epi8 (VPMOVDB)。有趣的事实:如果您愿意,它甚至可以以内存目标存储形式出现,尽管在 Skylake-avx512 硬件上,它的效率并不比正常情况高。 (它确实允许在没有 AVX512BW 的 Xeon Phi 上进行字节屏蔽存储。)

是的,如果您可以安全地读取每个字节元素末尾后的 3 个字节的垃圾 一个双字收集 + 打包是可能是你最好的选择。特别是如果它们不太可能跨越缓存行或尤其是页面边界进行拆分。如果您的索引偏向那些最坏情况的字节位置,请考虑以不同方式对齐源数据或执行其他操作。

如果索引中有任何类型的模式,则可以更有效地手动加载 + 洗牌,尤其是当单个向量加载可以跨越您想要的多个值时。即使只有一个固定的步幅,也值得考虑遍历索引以使用 vpinsrb 或其他方式一次插入一个元素,如 AVX2 byte gather with uint16 indices, into a __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.
}

对于 _mm256_i32gather_epi32 的 AVX2 版本,您必须使用不同的随机播放。也许提取高半部分,左移它,单词混合(vpblendw)所以你想要的所有字节都在一个 __m128i 中。然后vpshufb(_mm_shuffle_epi8)将你想要的8个字节pack到寄存器底部?

在聚集之前从高半部分的索引中减去一个或两个可以避免需要移位,因此您想要的字节位于 dword 元素中的不同位置。但请注意,这意味着如果 index=0 您是从 table 开始之前加载的。所以如果那可能会出现段错误,你就不能这样做。 (这对性能来说可能是个坏主意)。


如果您有多个这样的向量,并希望最终从 4 个偏移量向量 构建一个 __m512i 字节,您可以考虑使用 2-input pack指令(如 _mm512_packs_epi32 vpackssdw)和最终的 qword 洗牌来修复车道内行为。但是那些包只有饱和版本,没有截断,而且首先清除每个输入的高垃圾会花费额外的指令。

相反,也许最好在第一步中使用 _mm512_permutex2var_epi16 (vpermt2w),尽管它在 Skylake-X 上需要多次随机播放,不幸的是甚至在 vpermb 是单机。您想要计算从 4 个 __m512i 输入中产生一个 __m512i 的总洗牌 uops 并查看哪一个最便宜,为此与使用 [=11= 截断为 __m128i ] 然后建立备份。