首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >混洗向量的紧凑存储:解开4个字节,使用字节混洗来混洗uint32_t元素

混洗向量的紧凑存储:解开4个字节,使用字节混洗来混洗uint32_t元素
EN

Stack Overflow用户
提问于 2021-09-28 20:53:56
回答 1查看 90关注 0票数 1

我有一个跨体系结构的代码,它通过索引查找混洗,用于在向量中移动uint32_t元素。每次混洗都需要一个完整的向量常量,但只有4个字节的非冗余信息。(或者实际上是4x2比特的信息,但解包的成本更高。)

在SSSE3-SSE4.2上,我使用的是_mm_shuffle_epi8,而在arm上,我使用的是table

但是,现在我存储了整个混洗掩码,也就是控制向量,例如,对于int的标识,我将存储:0x0f0e0d0c0b0a09080706050403020100

我只想存储0x03020100,每个唯一的shuffle控制元素都存储在一个字节/ uint8_t中。

有没有从一个到另一个的有效方法?convert + multiply看起来有点笨重。

EN

回答 1

Stack Overflow用户

发布于 2021-09-28 21:43:15

存储您打包的LUT,每个字节保存起始字节号,这样您就不需要扩展它们。

将每个控制索引广播到相应元素的字节中(1个固定的混洗),然后添加一个常量set1_epi32(0x03020100)来偏移它们。

代码语言:javascript
运行
复制
  __m128i v = _mm_cvtsi32_si128(shuffle_lut[i]);

  v = _mm_shuffle_epi8(v, _mm_set1_epi32(0x03030303, 0x02020202, 0x01010101, 0x00000000));  // broadcast each byte into a dword
  v = _mm_add_epi8(v, _mm_set1_epi32(0x03020100));   // offset the byte indices

 // v is your shuffle-control vector, usable with another pshufb
 // as if you'd just unpacked lut[i]>>2 to dwords for vpermilps

身份混洗被存储为0x0c080400。顶部元素的顶部字节中的0x0c + 0x03 = 0x0f。

我猜C中的LUT实际上是作为uint32_t shuffle_lut完成的,在这种情况下,您不必担心执行严格别名安全的dword加载。对此的内部支持是不确定的,但movd_mm_cvtsi32_si128很容易使用。它接受一个值(不是地址),所以在C语言中,内存访问是在纯C中发生的。编译器仍然可以将加载合并到movd的内存操作数中。

顺便说一句,我假设你说的是SSE4.2,因为AVX1有_mm_permutevar_ps (vpermilps),所以_mm_cvtepu8_epi32 (pmovzxbd)可以解压4字节的加载,而不需要进一步的修改。使用双字索引,而不是字节索引,因此您可以将标识无序存储为0x03020100

不幸的是,让编译器从内部代码发出内存源vpmovzxbd xmm0, [rdi]指令对于除了clang之外的编译器来说是一件痛苦的事情。它们通常无法将movdmovq load内部函数合并到内存源操作数中,但如果您不想在调试构建中超过缓冲区的末尾,则必须使用它而不是完整的__m128i load。有关几年前的实际编译器结果,请参阅Loading 8 chars from memory into an __m256 variable as packed single precision floats

将AVX2或BMI2+AVX打包到单个字节中

每个混洗索引实际上只有2比特的信息,所以4个索引可以打包成1个字节(uint8_t)。

解包的方法是使用BMI2整数pdep。即_pdep_u32(lut[i], 0x03030303。然后是vmovd / vpmovzxbd / vpermilps。由于vpermilps只关心每个双字的低2位,因此甚至可以用乘数常量替换pdep

但在Zen3之前,pext在AMD上的速度非常慢。即使在Intel上,首先加载到整数中也会有很大的延迟。

另一种选择是使用AVX2变量移位将适当的2位带到每个双字元素的底部。从字节的广播加载开始。或者更有效地在大多数情况下(除了高速缓存线分割),CPU可以在加载端口中“免费”进行的双字广播,不需要单独的ALU混洗操作。(https://uops.info/)

这是一个痛苦的避免严格别名UB,例如,_mm_set1_epi32( *(uint32_t*) &lut[i] )是不安全的。但是有一个内部函数,它接受一个指针_mm_broadcast_ss

代码语言:javascript
运行
复制
  // make sure LUT[] doesn't end right at the end of a page
  // so we can broadcast-load 4 bytes starting at any byte offset in it.
  // i.e. pad it by 3 bytes if needed.
  __m128i v = _mm_castps_si128( _mm_broadcast_ss( (const float*)&LUT[i] ));

  // alternative:  __m128i v = _mm_set1_epi8( LUT[i] );  // vpbroadcastb is an extra shuffle uop, but narrower load

  v = _mm_srlv_epi32(v, _mm_set_epi32(6, 4, 2, 0));

  // ready for _mm_permutevar_ps
 // low 2 bits of each 32-bit element of v are correct

没有必要使用_mm_and_si128vpermilps并不关心控制向量元素中的高垃圾。

请注意,没有AVX2 vpermd的XMM版本,因此即使有AVX2可用,vpermilps仍然是使用32位粒度的变量控制混洗的最佳选择。

(除非你想将整个算法扩展到一个__m256i中的8个元素,那么是的,使用车道交叉vpermd,也就是_mm256_permutexvar_epi32。但是,您需要8 x 3位的混洗控制数据=3字节,而不是1。然后,可能仍然有太多的可能性来进行LUT。)

相关的还有:

票数 3
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/69368358

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档