如何用Intel AVX-512 intrinsics高效转换64个uint8_t为float并实现浮点乘法
问题描述
我分配了一段uint8_t内存:
uint8_t* srcPtr = malloc( sizeof( uint8_t ) * 64 );
srcPtr是测试函数的输入,需要和用户给定的单精度浮点数(比如9.0)相乘。
标量实现代码
for (int i = 0; i < 64; i ++) srcPtr[i] *= float_value;
为了用AVX-512 intrinsics实现这个乘法,需要把每个uint8_t转换成float。目前我用的是下面这段指令较多的代码,想知道有没有办法直接把64个uint8_t加载为float类型的AVX-512向量,简化操作?
当前使用的Intrinsics代码
__m512i px = _mm512_loadu_si512((__m512i *)srcPtr); __m128i input[4]; input[0] = _mm512_extracti32x4_epi32(px, 0); input[1] = _mm512_extracti32x4_epi32(px, 1); input[2] = _mm512_extracti32x4_epi32(px, 2); input[3] = _mm512_extracti32x4_epi32(px, 3); __m512i output[4]; output[0] = _mm512_cvtepu8_epi32(input[0]); output[1] = _mm512_cvtepu8_epi32(input[1]); output[2] = _mm512_cvtepu8_epi32(input[2]); output[3] = _mm512_cvtepu8_epi32(input[3]); __m512 p[4]; p[0] = _mm512_cvtepu32_ps(output[0]); p[1] = _mm512_cvtepu32_ps(output[1]); p[2] = _mm512_cvtepu32_ps(output[2]); p[3] = _mm512_cvtepu32_ps(output[3]);
解决方案
当然可以简化,不需要手动拆分再逐个转换。AVX-512提供了更高效的无符号整数扩展转换指令,能大幅精简代码:
方法1:批量扩展转换(基础AVX-512)
直接对加载后的__m512i向量分块处理,用_mm512_extracti64x2_epi64提取16字节块,再完成uint8_t→uint32_t→float的转换:
__m512i px = _mm512_loadu_si512((__m512i*)srcPtr); // 拆分64个uint8_t为4组,每组16个,扩展为uint32_t向量 __m512i vec0 = _mm512_cvtepu8_epi32(_mm512_extracti64x2_epi64(px, 0)); __m512i vec1 = _mm512_cvtepu8_epi32(_mm512_extracti64x2_epi64(px, 1)); __m512i vec2 = _mm512_cvtepu8_epi32(_mm512_extracti64x2_epi64(px, 2)); __m512i vec3 = _mm512_cvtepu8_epi32(_mm512_extracti64x2_epi64(px, 3)); // 转换为单精度浮点数向量 __m512 p0 = _mm512_cvtepu32_ps(vec0); __m512 p1 = _mm512_cvtepu32_ps(vec1); __m512 p2 = _mm512_cvtepu32_ps(vec2); __m512 p3 = _mm512_cvtepu32_ps(vec3);
方法2:利用AVX-512VL指令集(支持的CPU)
如果CPU支持AVX-512VL(可变长度),可以用256位扩展指令进一步简化提取操作:
__m512i px = _mm512_loadu_si512((__m512i*)srcPtr); // 提取4组16字节的uint8_t数据 __m256i low16 = _mm512_castsi512_si256(px); __m256i mid16 = _mm512_extracti32x4_epi32(px, 1); __m256i high16 = _mm512_extracti32x4_epi32(px, 2); __m256i highest16 = _mm512_extracti32x4_epi32(px, 3); // 一步完成uint8_t→uint32_t→float转换 __m512 p0 = _mm512_cvtepu32_ps(_mm256_cvtepu8_epi32(low16)); __m512 p1 = _mm512_cvtepu32_ps(_mm256_cvtepu8_epi32(mid16)); __m512 p2 = _mm512_cvtepu32_ps(_mm256_cvtepu8_epi32(high16)); __m512 p3 = _mm512_cvtepu32_ps(_mm256_cvtepu8_epi32(highest16));
后续乘法与结果压缩
完成转换后,广播标量浮点数并执行乘法,最后将结果压缩回uint8_t存回内存:
// 广播标量float为AVX-512向量 __m512 scalar_vec = _mm512_set1_ps(float_value); p0 = _mm512_mul_ps(p0, scalar_vec); p1 = _mm512_mul_ps(p1, scalar_vec); p2 = _mm512_mul_ps(p2, scalar_vec); p3 = _mm512_mul_ps(p3, scalar_vec); // 将float转换为uint32_t,再逐步压缩为uint8_t __m512i res0 = _mm512_cvtps_epu32(p0); __m512i res1 = _mm512_cvtps_epu32(p1); __m512i res2 = _mm512_cvtps_epu32(p2); __m512i res3 = _mm512_cvtps_epu32(p3); __m256i pack01 = _mm512_packus_epi32(res0, res1); __m256i pack23 = _mm512_packus_epi32(res2, res3); __m128i pack_low = _mm256_packus_epi16(_mm256_castsi256_si128(pack01), _mm256_extracti128_si256(pack01, 1)); __m128i pack_high = _mm256_packus_epi16(_mm256_castsi256_si128(pack23), _mm256_extracti128_si256(pack23, 1)); __m512i final_res = _mm512_set_m128i(pack_high, pack_low); _mm512_storeu_si512((__m512i*)srcPtr, final_res);
整个流程避免了手动拆分数组的冗余操作,代码更紧凑,执行效率也更高。
内容的提问来源于stack exchange,提问作者AI-developer
相关产品推荐
相关产品推荐

