将32位解压缩为32字节SIMD向量的最快方法

时间:2014-06-15 01:27:28

标签: x86 simd avx avx2

将32位存储在内存中的uint32_t中,将每个位解压缩到AVX寄存器的单独字节元素的最快方法是什么?这些位可以位于各自字节中的任何位置。

编辑:澄清一下,我的意思是位0进入字节0,位1到字节1.显然,字节内的所有其他位都为零。我现在最好的是2 PSHUFB并且每个位置都有一个掩码寄存器。

如果uint32_t是位图,则相应的向量元素应为0或非0。 (即我们可以得到一个带有vpcmpeqb的向量掩码,对着一个全零的向量)。

https://software.intel.com/en-us/forums/topic/283382

1 个答案:

答案 0 :(得分:10)

将32位整数x的32位“广播”到256位YMM寄存器z的32个字节或两个128位XMM寄存器的{16} {{1} }}和z_low您可以执行以下操作。

使用AVX2:

z_high

没有AVX2,最好用SSE做到这一点:

__m256i y = _mm256_set1_epi32(x);
__m256i z = _mm256_shuffle_epi8(y,mask1);
z = _mm256_and_si256(z,mask2);

面具和工作示例如下所示。如果你打算多次这样做,你应该这样做 定义主循环外的掩码。

__m128i y = _mm_set1_epi32(x);      
__m128i z_low  = _mm_shuffle_epi8(y,mask_low);
__m128i z_high = _mm_shuffle_epi8(y,mask_high); 
z_low  = _mm_and_si128(z_low ,mask2);
z_high = _mm_and_si128(z_high,mask2);

在每个向量元素中得到0或-1:

对全零进行一次额外步骤#include <immintrin.h> #include <stdio.h> int main() { int x = 0x87654321; static const char mask1a[32] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03 }; static const char mask2a[32] = { 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, }; char out[32]; #if defined ( __AVX2__ ) __m256i mask2 = _mm256_loadu_si256((__m256i*)mask2a); __m256i mask1 = _mm256_loadu_si256((__m256i*)mask1a); __m256i y = _mm256_set1_epi32(x); __m256i z = _mm256_shuffle_epi8(y,mask1); z = _mm256_and_si256(z,mask2); _mm256_storeu_si256((__m256i*)out,z); #else __m128i mask2 = _mm_loadu_si128((__m128i*)mask2a); __m128i mask_low = _mm_loadu_si128((__m128i*)&mask1a[ 0]); __m128i mask_high = _mm_loadu_si128((__m128i*)&mask1a[16]); __m128i y = _mm_set1_epi32(x); __m128i z_low = _mm_shuffle_epi8(y,mask_low); __m128i z_high = _mm_shuffle_epi8(y,mask_high); z_low = _mm_and_si128(z_low,mask2); z_high = _mm_and_si128(z_high,mask2); _mm_storeu_si128((__m128i*)&out[ 0],z_low); _mm_storeu_si128((__m128i*)&out[16],z_high); #endif for(int i=0; i<8; i++) { for(int j=0; j<4; j++) { printf("%x ", out[4*i+j]); }printf("\n"); } printf("\n"); } 。任何非零变为0,零变为-1。如果我们不想要这种反转,请使用_mm256_cmpeq_epi8代替andnot。它反转了它的第一个操作数。

and

Godbolt Compiler Explorer上查看。