混合4宽与8宽指令是否严重影响AVX性能?代码优化求助
我编写的AVX代码运行速度远慢于SSE4版本,理论上AVX版本指令数翻倍但处理量也翻倍,性能应与SSE4版本相当,但实际运行速度堪比标量版本。以下是各版本的汇编代码:
GCC 13.1生成的SSE4循环汇编
.L6: movaps xmm1, XMMWORD PTR [rbx+rsi] movaps xmm3, XMMWORD PTR [rbp+0+rsi] movaps xmm2, xmm4 lea eax, [0+rcx*4] movd xmm0, eax add rcx, 1 add rsi, 16 addps xmm3, xmm1 cmpleps xmm1, xmm5 pshufd xmm0, xmm0, 0 paddd xmm0, xmm6 cmpleps xmm2, xmm3 pand xmm1, xmm2 movmskps edi, xmm1 mov rax, rdi sal rdi, 4 pshufb xmm0, XMMWORD PTR shufmasks.0[rdi] popcnt eax, eax movups XMMWORD PTR [r8], xmm0 lea r8, [r8+rax*4] cmp rcx, rdx jne .L6
GCC 13.1生成的AVX循环汇编
.L6: lea eax, [0+rcx*8] vmovaps ymm7, YMMWORD PTR [rbx+rsi] xor r10d, r10d add rcx, 1 vmovd xmm1, eax xor eax, eax vpshufd xmm0, xmm1, 0 vcmpleps ymm6, ymm7, ymm3 vmovdqa xmm1, xmm0 vpaddd xmm0, xmm0, xmm5 vpaddd xmm1, xmm1, xmm2 vinsertf128 ymm0, ymm0, xmm1, 0x1 vaddps ymm1, ymm7, YMMWORD PTR [r12+rsi] add rsi, 32 vcmpleps ymm1, ymm4, ymm1 vandps ymm1, ymm1, ymm6 vmovaps xmm6, xmm0 vextractf128 xmm0, ymm0, 0x1 vmovaps xmm7, xmm1 vextractf128 xmm1, ymm1, 0x1 vmovmskps edx, xmm7 popcnt eax, edx sal rdx, 4 vpshufb xmm6, xmm6, XMMWORD PTR shufmasks.0[rdx] vmovmskps edx, xmm1 popcnt r10d, edx sal rdx, 4 vmovdqa XMMWORD PTR [rsp+32], xmm6 vpshufb xmm0, xmm0, XMMWORD PTR shufmasks.0[rdx] movsx rdx, eax add eax, r10d vmovups XMMWORD PTR [rsp+32+rdx*4], xmm0 vmovdqa ymm6, YMMWORD PTR [rsp+32] cdqe vmovdqu YMMWORD PTR [rdi], ymm6 lea rdi, [rdi+rax*4] cmp rcx, r8 jne .L6
GCC 12.2.0生成的AVX循环汇编
.L4: vmovaps ymm11, YMMWORD PTR [rbx+rsi] vaddps ymm12, ymm11, YMMWORD PTR [r12+rsi] xor eax, eax add rsi, 32 lea r11d, 0[0+rcx*8] add rcx, 1 vcmpleps ymm14, ymm11, ymm3 vmovd xmm1, r11d xor r11d, r11d vcmpleps ymm13, ymm4, ymm12 vpshufd xmm0, xmm1, 0 vpaddd xmm7, xmm0, xmm5 vpaddd xmm9, xmm0, xmm8 vinsertf128 ymm10, ymm7, xmm9, 0x1 vandps ymm15, ymm13, ymm14 vextractf128 xmm0, ymm10, 0x1 vextractf128 xmm1, ymm15, 0x1 vmovmskps r13d, xmm15 vmovmskps r14d, xmm1 popcnt eax, r13d sal r13, 4 movsx rdx, eax popcnt r11d, r14d sal r14, 4 vpshufb xmm7, xmm10, XMMWORD PTR [r8+r13] add eax, r11d vpshufb xmm9, xmm0, XMMWORD PTR [r8+r14] vmovdqa XMMWORD PTR 32[rsp], xmm7 cdqe vmovups XMMWORD PTR 32[rsp+rdx*4], xmm9 vmovdqa ymm10, YMMWORD PTR 32[rsp] vmovdqu YMMWORD PTR [rdi], ymm10 lea rdi, [rdi+rax*4] cmp rcx, r9 jne .L4
核心性能瓶颈分析
栈内存临时存储的额外开销
AVX版本频繁使用栈内存([rsp+32])作为临时缓冲区,存储拆分后的SSE结果再合并。栈内存的读写会引入延迟,且vmovdqu非对齐加载指令在循环中反复触发,进一步增加内存访问开销。而SSE版本直接将结果写入目标寄存器,无中间栈存储步骤。YMM/XMM寄存器切换的流水线断裂
代码中频繁调用vextractf128和vinsertf128在YMM与XMM寄存器间拆分、合并数据,这类指令本身有延迟,还会导致CPU的AVX与SSE执行单元频繁切换,破坏指令流水线的连续性,部分CPU还会产生模式切换的额外周期损耗。数据依赖链过长导致流水线停滞
AVX版本拆分后的popcnt、pshufb操作形成了更长的依赖链:从YMM拆分到XMM,再做掩码计算、洗牌,最后合并写入。相比SSE版本的单条依赖链,AVX的两条并行依赖链加合并步骤,导致指令等待时间变长,流水线停滞概率升高。寄存器调度效率低下
AVX版本使用了更多寄存器,但编译器生成的代码指令顺序不合理(如vaddps放在插入操作之后),没有充分利用CPU的乱序执行能力,导致执行单元空闲等待。
优化建议
移除栈内存临时存储
直接用两个XMM寄存器存储拆分后的结果,最后通过vinsertf128合并为YMM寄存器再写入目标内存,彻底避免栈的读写操作。重排指令顺序,利用乱序执行
将浮点运算(vmovaps、vaddps、vcmpleps)与整数拆分操作并行安排,让CPU的浮点单元和整数单元同时工作,减少流水线停滞。优化掩码提取逻辑
使用vpmovmskb直接将YMM寄存器的掩码转为64位整数,再拆分为两个32位整数处理,替代vextractf128+vmovmskps的组合,减少指令数量。调整编译器优化选项
尝试添加-mavx2(CPU支持时)、-O3、-ffast-math、-fno-tree-vectorize等选项,引导编译器生成更高效的AVX代码;也可尝试clang编译器,其AVX指令调度通常更优。合并结果写入步骤
计算出两个XMM的结果后,直接合并为YMM写入目标内存,减少内存操作次数。
内容的提问来源于stack exchange,提问作者aganm

