我需要优化以下压缩操作(在具有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位。uint16_t
元素而不是8x uint32_t
。现在你有16位元素保存适合uint8_t
的值而不会溢出。_mm256_packus_epi16
(带符号输入,输出的无符号饱和度)将向量对下载到8位。这实际上很重要,packs
将剪切一些有效值,因为您的数据使用全范围的uint8_t
。__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
开始向左移一个向左移位。但是我们可能最好用左移,因为vpslld
或vpsrld
的EVEX编码可以获取数据与VEX编码不同,它具有即时移位计数。所以我们希望我们得到一个微型融合负载+移位uop来节省前端带宽。
另一种选择是移位+混合,导致字节交错的结果更难以修复,除非你不介意这个顺序。
字节粒度混合(不含AVX512BW)需要vpblendvb
,即2 uops。 (并且Haswell只在端口5上运行,因此可能是一个巨大的瓶颈。在SKL上,对于任何矢量ALU端口,它都是2 uops。)