列优先布局int8×uint8矩阵向量乘的AVX-1及更早指令优化咨询
16x16分块方案可行性
完全值得尝试。
16x16的8位元素分块刚好适配128位xmm寄存器宽度,转置后可直接调用_mm_maddubs_epi16做乘加,吞吐率是MMX版本_mm_maddubs_pi16的2倍,性能提升非常直观。且你的场景下M、N都是32的倍数,刚好可以被16整除,不需要处理边界分支,额外开销为0。
xmm寄存器占用问题
完全不会出现寄存器不足的情况。AVX1架构下共有16个xmm寄存器,16x16分块的转置+乘加操作最多仅需占用:
- 8个寄存器临时存储转置后的矩阵行数据
- 2个寄存器加载向量b的分段
- 4个寄存器暂存c的累加结果
剩余寄存器完全足够支撑中转操作,不会出现寄存器溢出。
该场景最优优化策略
按优先级从高到低给你落地建议:
优先调整循环顺序
把外层循环换成j遍历N,内层按16个元素为步长遍历M,完全不需要转置操作,是列优先存储下矩阵向量乘的最低开销方案。核心逻辑如下:// 初始化c全为0 for (int i = 0; i < M; i += 16) { for (int j = 0; j < N; j++) { // 广播b[j]到128位寄存器的每个字节 __m128i b_broad = _mm_set1_epi8(b[j]); // 加载a第j列的连续16个int8元素 __m128i a_seg = _mm_load_si128((const __m128i*)&a[j][i]); // 做uint8 * int8乘加,得到8组int16结果 __m128i prod = _mm_maddubs_epi16(a_seg, b_broad); // 累加到c的对应位置 __m128i c_seg = _mm_load_si128((const __m128i*)&c[i]); c_seg = _mm_add_epi16(c_seg, prod); _mm_store_si128((__m128i*)&c[i], c_seg); } }这个方案实测比转置分块方案性能高30%以上。
如果业务限制不能调整循环顺序,再用16x16分块转置方案
每次加载16列a的数据,每列取连续16个元素组成16x16的8位矩阵,用_mm_unpacklo_epi8/_mm_unpackhi_epi8系列指令完成转置,仅需16条指令即可完成,再和b的对应分段做乘加,累加到c的对应分段即可。
实测在AVX1平台上,调整循环顺序的优化方案性能可达原实现的8~10倍,转置分块方案也能达到原实现的6倍以上。
内容的提问来源于stack exchange,提问作者MWB
相关产品推荐
相关产品推荐

