如何在不使用AVX-512的前提下通过AVX实现int64转int32
问题:无AVX-512环境下__m256i(4个int64)转__m128i(4个int32)的最优实现
我需要将存储了4个long(64位)的寄存器转换/打包为4个int(32位),也就是将int64类型的__m256i转换为int32类型的__m128i。
我当前环境不支持AVX-512,因此无法使用如下内置函数:
__m256i n; __m128i r128i = _mm256_cvtepi64_epi32( n );
请问有没有比下述两种方案更优的替代实现?
现有方案1:丢失向量化的落地方案
__m256i n; alignas(32) int64_t temp[4]; _mm256_store_si256((__m256i*)temp, n); int32_t a = (int32_t)temp[0]; int32_t b = (int32_t)temp[1]; int32_t c = (int32_t)temp[2]; int32_t d = (int32_t)temp[3]; __m128i r128i = _mm_set_epi32(a, b, c, d);
现有方案2:错误打包为16位整数的方案
__m128i lo_lane = _mm256_castsi256_si128(n); __m128i hi_lane = _mm256_extracti128_si256(n, 1); __m128i r128i = _mm_packus_epi32(lo_lane, hi_lane);
答案
以下两种纯向量化实现均无内存访问操作,性能远优于现有落地方案,可根据转换逻辑选择:
方案A:直接截断低32位(性能最优,仅2条指令)
如果业务逻辑只需要取每个int64的低32位,不需要做范围饱和处理,优先用这个方案:
// 按索引提取4个int64的低32位,放到__m256i的低128位区域 __m256i perm = _mm256_permutevar8x32_epi32(n, _mm256_setr_epi32(0, 2, 4, 6, 0, 0, 0, 0)); // 直接取低128位即为目标结果 __m128i res = _mm256_castsi256_si128(perm);
方案B:有符号饱和转换(和AVX-512原生函数行为完全对齐)
如果需要和_mm256_cvtepi64_epi32的行为一致,超出int32范围的数值自动饱和到INT32_MAX/INT32_MIN,用这个方案:
// 拆分256位寄存器的高低128位lane __m128i lo = _mm256_castsi256_si128(n); __m128i hi = _mm256_extracti128_si256(n, 1); // 分别对每个lane的两个int64做饱和转换、调整元素顺序 lo = _mm_shuffle_epi32(_mm_packs_epi32(lo, lo), _MM_SHUFFLE(3, 1, 2, 0)); hi = _mm_shuffle_epi32(_mm_packs_epi32(hi, hi), _MM_SHUFFLE(3, 1, 2, 0)); // 合并两个lane的结果得到最终的4个连续int32 __m128i res = _mm_unpacklo_epi64(lo, hi);
原有方案的问题说明
- 落地方案:存在store-to-load转发延迟,至少需要10个时钟周期,且会占用内存带宽,在循环逻辑中性能会下降3~4倍。
- 错误打包方案:
_mm_packus_epi32的作用是将2个128位寄存器的8个int32饱和转换为8个uint16,输出元素位宽为16位,完全不符合32位输出的需求。
内容的提问来源于stack exchange,提问作者arkan
相关产品推荐
相关产品推荐

