如何有效地重新排序 __m256i 向量的字节(将 int32_t 转换为 uint8_t)?

How do I efficiently reorder bytes of a __m256i vector (convert int32_t to uint8_t)?

我需要优化以下压缩操作(在可用 AVX2 指令的服务器上):

取浮点数组的指数,移位并存储到 uint8_t 数组

经验不足,建议从https://github.com/feltor-dev/vcl库入手

现在我有

uint8_t* uin8_t_ptr = ...;
float* float_ptr = ...;
float* final_ptr = float_ptr + offset;

for (; float_ptr < final_ptr; float_ptr+=8) {
    Vec8f vec_f = Vec8f().load(float_ptr);
    Vec8i vec_i = fraction(vec_f) + 128; // range: 0~255
    ...
}

我的问题是如何有效地将 vec_i 结果存储到 uint8_t 数组?

我无法在 vcl 库中找到相关函数,并且试图探索内部指令,因为我可以访问 __m256i 数据。

我目前的理解是使用 _mm256_shuffle_epi8 之类的东西,但不知道最有效的方法。

我想知道是否尝试充分利用这些位并每次存储 32 个元素(使用 float_ptr+=32 的循环)是否可行。

欢迎提出任何建议。谢谢。

可能最好的矢量化方法是使用 vpackssdw / vpackuswbvpermd 作为车道内包后的车道交叉修复.

  • _mm256_srli_epi32 将指数(和符号位)移到每个 32 位元素的底部。无论符号位如何,逻辑移位都会留下非负结果。
  • 然后用_mm256_packs_epi32(有符号输入,有符号输出饱和)将向量对压缩到 16 位。
  • 然后屏蔽符号位,留下一个8位指数。我们一直等到现在,这样我们就可以每条指令执行 16x uint16_t 个元素,而不是 8x uint32_t。现在你有 16 位元素保存适合 uint8_t 的值而不会溢出。
  • 然后用_mm256_packus_epi16将向量对压缩到8位(有符号输入,无符号输出饱和) .这实际上很重要,packs 会剪切一些有效值,因为您的数据使用了 uint8_t.
  • 的全部范围
  • VPERMD 打乱来自 4x 256 位输入向量的每个通道的该向量的八个 32 位块。 __m256i lanefix = _mm256_permutevar8x32_epi32(abcd, _mm256_setr_epi32(0,4, 1,5, 2,6, 3,7)); shuffle 与 完全相同,它在使用 FP->int 转换而不是右移来获取指数字段后执行相同的打包。

每个结果向量,你有 4x load+shift(vpsrld ymm,[mem] 希望),2x vpackssdw 洗牌,2x vpand mask,1x vpackuswb,和 1x vpermd。那是 4 次洗牌,所以我们对 Intel HSW/SKL 的最好期望是每 4 个时钟 1 个结果向量。 (Ryzen 具有更好的洗牌吞吐量,除了 vpermd 是昂贵的。)

但这应该是可以实现的,所以平均每个时钟 32 字节输入/8 字节输出。

总共 10 个向量 ALU 微指令(包括微融合负载 + ALU)和 1 个存储应该能够在那个时间执行。在前端成为比 shuffle 更严重的瓶颈之前,我们有 16 个总 uops 的空间,包括循环开销。

更新:哎呀,我忘了计算无偏指数;这将需要额外的 add。但是您可以在压缩到 8 位后执行此操作。(并将其优化为 XOR)。我不认为我们可以将它优化掉或变成其他东西,比如屏蔽掉符号位。

使用 AVX512BW,您可以执行字节粒度 vpaddb 来消除偏差,使用零屏蔽将每对的高字节归零。这会将无偏差折叠到 16 位掩码中。


AVX512F也有vpmovdb32->8位截断(不饱和),但只针对单输入。因此,您将从一个 256 位或 512 位向量输入得到一个 64 位或 128 位结果,每个输入有 1 次随机播放 + 1 次添加,而不是 2+1 次随机播放 + 2 次零屏蔽 vpaddb 每个输入向量。 (两者都需要每个输入向量右移以将 8 位指数字段与双字底部的字节边界对齐)

使用AVX512VBMI,vpermt2b would let us grab bytes from 2 input vectors. But it costs 2 uops on CannonLake, so only useful on hypothetical future CPUs if it gets cheaper. They can be the top byte of a dword, so we could start with vpaddd a vector to itself to left-shift by 1. But we're probably best with a left-shift because the EVEX encoding of vpslldvpsrld可以从内存中取出数据,立即移位计数,这与VEX编码不同.所以希望我们得到一个单一的微融合负载+移位uop来节省前端带宽。


另一种选择是 shift + 混合,导致字节交错结果的修复成本更高,除非您不介意该顺序。

字节粒度混合(没有 AVX512BW)需要 vpblendvb,即 2 微指令。 (并且在 Haswell 上仅在端口 5 上运行,因此可能是一个巨大的瓶颈。在 SKL 上,任何向量 ALU 端口都是 2 微指令。)