我需要优化以下压缩操作(在具有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的循环)是
欢迎提出任何建议。谢谢。
答案 0 :(得分:2)
可能最好的矢量化方法是将vpackssdw
/ vpackuswb
和vpermd
用作车道内装填后的车道固定装置。
_mm256_srli_epi32
将每个32位元素的指数(和符号位)移至底部。不管符号位如何,逻辑移位都会留下非负结果。_mm256_packs_epi32
(带符号输入,带符号饱和输出)将向量对压缩至16位。uint16_t
个元素,而不是8x uint32_t
。现在,您拥有16位元素,这些元素的值适合uint8_t
而不会溢出。_mm256_packus_epi16
(有符号输入,输出为 unsigned 饱和)将向量对压缩至8位。这实际上很重要,packs
会裁剪一些有效值,因为您的数据使用了整个uint8_t
范围。__m256i lanefix = _mm256_permutevar8x32_epi32(abcd, _mm256_setr_epi32(0,4, 1,5, 2,6, 3,7));
完全相同,在使用FP-> int转换而不是右移以获取指数字段之后,进行了相同的打包。每个结果向量具有4倍的load + shift(vpsrld ymm,[mem]
),2倍的vpackssdw
随机播放,2倍的vpand
掩码,1倍的vpackuswb
和1倍的{{1 }}。那是4次改组,因此我们希望英特尔HSW / SKL上最好的结果是每4个时钟1个结果向量。 (Ryzen的洗牌吞吐量更好,除了vpermd
昂贵之外)。
但这应该是可以实现的,因此平均每个时钟32个输入字节/ 8个输出字节。
总共有10个矢量ALU运算符(包括微融合负载+ ALU),并且应该能够在该时间内执行1个存储。在前端变得比混洗更严重的瓶颈之前,我们有16个总共ouop的空间,包括循环开销。
update:哎呀,我忘了数不偏指数。这将需要额外的vpermd
。但是您可以在压缩到8位后执行此操作。(并将其优化为XOR)。我认为我们无法对其进行优化或将其优化,例如掩盖符号位。
使用AVX512BW,您可以对无偏进行字节粒度add
,并通过零掩码将每对的高字节清零。这样会将无偏向折叠到16位掩码中。
AVX512F也具有vpmovdb
32-> 8位截断(不饱和),但仅适用于单个输入。因此,您将从一个输入256或512位向量中获得一个64位或128位结果,其中每个输入1个混洗+ 1个添加,而不是2 + 1个混洗+每个输入2个零掩码vpaddb
向量。 (两者都需要每个输入向量右移,以将8位指数字段与dword底部的字节边界对齐)
使用 AVX512VBMI,vpermt2b
将允许我们从2个输入向量中获取字节。但是在CannonLake上它的价格为2微秒,因此仅在假设未来的CPU便宜时才有用。它们可以是dword的最高字节,因此我们可以从vpaddb
到其自身的向量开始,向左移1。但是最好向左移,因为EVEX编码为{{3 }}或vpaddd
可以从内存中获取具有立即移位计数的数据,这与VEX编码不同。因此,希望我们得到一个微融合的load + shift uop,以节省前端带宽。
另一种选择是移位+混合,导致字节交错的结果修复起来更加昂贵,除非您不介意该顺序。
并且字节粒度混合(不带AVX512BW)需要vpsrld
,它为2 oups。 (而且在Haswell上仅在端口5上运行,因此可能是一个巨大的瓶颈。在SKL上,任何矢量ALU端口的价格均为2微秒。)