AVX512:如何将32个16位字按顺序拆分为两组32位双字并打包?
解决方案:AVX512拆分16位字寄存器为32位双字寄存器
原代码中使用_mm512_unpacklo/hi_epi16无法满足需求的核心原因是:这类unpack指令在每个128位lane内部对16位元素进行交错拆分,而非按整个寄存器的前16/后16元素分割,因此得到的是分组交错的结果,不符合你的要求。
以下是两种高效且直观的实现方案:
方案1:拆分+扩展(推荐,逻辑清晰且性能最优)
先将原__m512i寄存器拆分为两个__m256i(各包含16个16位字),再分别将16位字零扩展为32位双字:
#include <immintrin.h> #include <stdio.h> int main() { unsigned short int Src[32], Dst[32]; int DstHi[16], DstLo[16]; for (int i = 0; i < 32; i++) Src[i] = i; __m512i src = _mm512_loadu_si512((__m512i*)Src); // 拆分原寄存器:取前256位(16个16位字)和后256位(16个16位字) __m256i src_lo_256 = _mm512_extracti64x4_epi64(src, 0); __m256i src_hi_256 = _mm512_extracti64x4_epi64(src, 1); // 将无符号16位字零扩展为32位双字,填充到__m512i寄存器 __m512i dst_lo = _mm512_cvtepu16_epi32(src_lo_256); __m512i dst_hi = _mm512_cvtepu16_epi32(src_hi_256); // 验证输出 _mm512_storeu_si512((__m512i*)DstLo, dst_lo); _mm512_storeu_si512((__m512i*)DstHi, dst_hi); printf("DstLo (前16个16位字扩展为32位):"); for (int i = 0; i < 16; i++) printf("%d,", DstLo[i]); printf("\nDstHi (后16个16位字扩展为32位):"); for (int i = 0; i < 16; i++) printf("%d,", DstHi[i]); // 可选:将32位双字压缩回16位字,验证数据完整性 __m512i dst = _mm512_packus_epi32(dst_lo, dst_hi); _mm512_storeu_si512((__m512i*)Dst, dst); printf("\nDst packed back:\n"); for (int i = 0; i < 32; i++) printf("%d;", Dst[i]); return 0; }
关键指令说明:
_mm512_extracti64x4_epi64:将__m512i按64位块拆分,参数0提取前4个64位块(共256位,对应原寄存器的前16个16位字),参数1提取后4个64位块。_mm512_cvtepu16_epi32:将__m256i中的16个无符号16位元素零扩展为16个32位元素,刚好填满一个__m512i寄存器,高位自动补0。
方案2:掩码重排(紧凑实现)
如果希望用更少的指令完成操作,可以利用_mm512_permutexvar_epi32配合预定义掩码直接重排元素:
#include <immintrin.h> #include <stdio.h> int main() { unsigned short int Src[32], Dst[32]; int DstHi[16], DstLo[16]; for (int i = 0; i < 32; i++) Src[i] = i; __m512i src = _mm512_loadu_si512((__m512i*)Src); // 预定义重排掩码:分别对应前16和后16个16位元素的扩展位置 const __m512i mask_lo = _mm512_set_epi32(7,6,5,4,3,2,1,0,15,14,13,12,11,10,9,8); const __m512i mask_hi = _mm512_set_epi32(23,22,21,20,19,18,17,16,31,30,29,28,27,26,25,24); // 将整个__m512i的32个16位元素零扩展为64个32位元素,存入两个__m512i __m512i src_epi32_lo = _mm512_cvtepu16_epi32(_mm512_castsi512_si256(src)); __m512i src_epi32_hi = _mm512_cvtepu16_epi32(_mm512_extracti64x4_epi64(src, 1)); // 合并扩展后的元素 __m512i src_epi32 = _mm512_inserti64x4_epi64(src_epi32_lo, src_epi32_hi, 1); // 按掩码重排得到目标寄存器 __m512i dst_lo = _mm512_permutexvar_epi32(mask_lo, src_epi32); __m512i dst_hi = _mm512_permutexvar_epi32(mask_hi, src_epi32); // 验证输出 _mm512_storeu_si512((__m512i*)DstLo, dst_lo); _mm512_storeu_si512((__m512i*)DstHi, dst_hi); printf("DstLo (前16个16位字扩展为32位):"); for (int i = 0; i < 16; i++) printf("%d,", DstLo[i]); printf("\nDstHi (后16个16位字扩展为32位):"); for (int i = 0; i < 16; i++) printf("%d,", DstHi[i]); return 0; }
说明:
- 先将原寄存器的前后两部分分别扩展为32位,再合并为一个1024位的虚拟向量(通过两个__m512i拼接),最后用掩码重排提取需要的元素。
- 该方案指令数略少,但逻辑相对复杂,性能与方案1接近,适合对代码紧凑性有要求的场景。
内容的提问来源于stack exchange,提问作者Andrey Dmitriev
相关产品推荐
相关产品推荐

