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

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

我需要优化以下压缩操作(在具有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的循环)将是要走的路。

欢迎任何建议。谢谢。

c++ vectorization simd intrinsics avx2
1个回答
3
投票

对于矢量化这个可能你最好的选择可能是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位输入向量通道的8个32位块进行混洗。与__m256i lanefix = _mm256_permutevar8x32_epi32(abcd, _mm256_setr_epi32(0,4, 1,5, 2,6, 3,7));完全相同的How to convert 32-bit float to 8-bit signed char? shuffle,在使用FP-> int转换而不是右移以获取指数字段后执行相同的包。

每个结果向量,你有4x负载+移位(vpsrld ymm,[mem]希望),2x vpackssdw shuffles,2x vpand面具,1x vpackuswb和1x vpermd。那是4次洗牌,所以我们希望英特尔HSW / SKL最好的是每4个时钟1个结果向量。 (Ryzen有更好的洗牌吞吐量,除了昂贵的vpermd。)

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

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

更新:哎呀,我忘了指数不偏不倚的指数;这需要额外的add。但是你可以在打包到8位后做到这一点。 (并将其优化为异或)。我不认为我们可以将它优化或其他东西,比如掩盖符号位。

使用AVX512BW,您可以执行字节粒度vpaddb到非偏移,使用零屏蔽将每对的高字节归零。这会将不偏不倚地折叠成16位掩模。


AVX512F还具有vpmovdb 32-> 8位截断(不饱和),但仅适用于单输入。因此,您将从一个输入256或512位向量获得一个64位或128位结果,每个输入使用1次shuffle + 1次添加,而不是每个输入向量使用2 + 1次shuffle + 2次零屏蔽vpaddb。 (两者都需要每个输入向量右移以将8位指数字段与双字底部的字节边界对齐)

使用AVX512VBMI,vpermt2b可以让我们从2个输入向量中获取字节。但它在CannonLake上的成本是2 uops,所以只有在假设的未来CPU变得更便宜时才有用。它们可以是dword的顶部字节,所以我们可以从vpaddd开始向左移一个向左移位。但是我们可能最好用左移,因为vpslldvpsrld的EVEX编码可以获取数据与VEX编码不同,它具有即时移位计数。所以我们希望我们得到一个微型融合负载+移位uop来节省前端带宽。


另一种选择是移位+混合,导致字节交错的结果更难以修复,除非你不介意这个顺序。

字节粒度混合(不含AVX512BW)需要vpblendvb,即2 uops。 (并且Haswell只在端口5上运行,因此可能是一个巨大的瓶颈。在SKL上,对于任何矢量ALU端口,它都是2 uops。)

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