c++ - 如何有效地重新排序 __m256i 向量的字节(将 int32_t 转换为 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
/ vpackuswb
,并且vpermd
作为车道内包后的车道交叉修复。
_mm256_srli_epi32
将指数(和符号位)移到每个 32 位元素的底部。无论符号位如何,逻辑移位都会留下非负结果。_mm256_packs_epi32
然后使用(有符号输入,有符号输出饱和)将向量对打包到 16 位。- 然后屏蔽掉符号位,留下一个 8 位指数。我们一直等到现在,所以我们可以在
uint16_t
每条指令中执行 16x 个元素,而不是 8xuint32_t
。现在,您有 16 位元素,其中包含适合uint8_t
而不会溢出的值。 _mm256_packus_epi16
然后用(有符号输入,无符号输出饱和)将向量对压缩到 8 位。这实际上很重要,packs
会裁剪一些有效值,因为您的数据使用uint8_t
.- VPERMD将来自 4x 256 位输入向量的每个通道的该向量的八个 32 位块打乱。与如何将 32 位浮点数转换为 8 位有符号字符中的完全一样的
__m256i lanefix = _mm256_permutevar8x32_epi32(abcd, _mm256_setr_epi32(0,4, 1,5, 2,6, 3,7));
洗牌?,它在使用 FP->int 转换而不是右移来获取指数字段后执行相同的打包。
对于每个结果向量,您有 4x load+shift(vpsrld ymm,[mem]
希望如此)、2x vpackssdw
shuffle、2x vpand
mask、1xvpackuswb
和 1x vpermd
。那是 4 次洗牌,所以我们在英特尔 HSW/SKL 上所能期望的最好结果是每 4 个时钟有 1 个结果向量。(Ryzen 具有更好的 shuffle 吞吐量,但vpermd
价格昂贵。)
但这应该是可以实现的,因此平均每个时钟 32 字节的输入 / 8 字节的输出。
总共 10 个向量 ALU 微指令(包括微融合加载+ALU),以及 1 个存储应该能够在那个时间内执行。在前端成为比 shuffle 更严重的瓶颈之前,我们总共有 16 个 uops 的空间,包括循环开销。
更新:哎呀,我忘了计算无偏指数;那将需要额外的add
. 但是您可以在打包到 8 位后执行此操作。 (并将其优化为 XOR)。我不认为我们可以将其优化掉或优化成其他东西,比如屏蔽掉符号位。
使用 AVX512BW,您可以对无偏进行字节粒度vpaddb
,使用零掩码将每对的高字节归零。这会将无偏折叠到 16 位掩码中。
AVX512F 还具有vpmovdb
32->8 位截断(无饱和),但仅适用于单输入。因此,您将从一个输入 256 或 512 位向量中获得一个 64 位或 128 位结果,每个输入 1 个 shuffle + 1 个 add 而不是 2+1 shuffle + 2 个零掩码vpaddb
每个输入向量。(两者都需要每个输入向量右移以将 8 位指数字段与 dword 底部的字节边界对齐)
使用AVX512VBMI,vpermt2b
我们可以从 2 个输入向量中获取字节。但它在 CannonLake 上的成本为 2 微秒,因此只有在假设的未来 CPU 变得更便宜时才有用。它们可以是 dword 的最高字节,因此我们可以从vpaddd
自身的向量开始左移 1。但我们可能最好使用左移,因为 EVEX 编码vpslld
orvpsrld
可以从内存中获取数据与 VEX 编码不同,立即移位计数。所以希望我们得到一个微融合的负载+移位uop来节省前端带宽。
另一种选择是移位+混合,导致字节交错的结果修复起来更昂贵,除非你不介意这个顺序。
字节粒度混合(没有 AVX512BW)需要vpblendvb
2 微秒。(并且在 Haswell 上仅在端口 5 上运行,因此可能是一个巨大的瓶颈。在 SKL 上,任何矢量 ALU 端口都是 2 微秒。)
推荐阅读
- php - 将旧记录从数据库表推送到页面底部,并将最新条目推送到页面顶部
- java - Firebase 在 for each 循环之后总是返回 null
- sql - SQL Server:查找最近的星期二
- android - 片段内的微调器
- java - 将文件上传到 PHP 源时发送参数
- android - 地理编码自动完成移动
- c# - 将按钮插入特定的数据网格视图列
- weblogic - WLST:访问部署>监控>工作负载>工作管理器的mBean
- java - 在 Flutter 中向特定于平台的代码添加依赖项
- python - PyParsing 问题:如何告诉 PyParsing 在命令行界面中等待更多字符串?