You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MIPS架构下指令重排后如何展开点积循环?

MIPS点积计算:指令调度与循环展开优化指南

原始代码

Loop: ld $f0, 0($s1) 
      ld $f4, 0($s2) 
      multd $f0, $f0, $f4 
      addd $f2, $f0, $f2 
      subi $s1, $s1, 8 
      subi $s2, $s2, 8 
      bneqz $s1, Loop

核心约束

延迟周期表

结果产生指令结果使用指令延迟周期
FP ALU操作其他FP ALU操作3
FP ALU操作Store Double2
Load DoubleFP ALU操作1
Load DoubleStore Double0

额外规则:单周期延迟分支(分支指令后有1个延迟槽,该槽指令无条件执行)

你的尝试代码

Loop: ld $f0, 0($s1) 
      ld $f4, 0($s2)
      STALL
      multd $f0, $f0, $f4
      STALL
      STALL
      STALL 
      addd $f2, $f0, $f2 
      subi $s1, $s1, 8 
      subi $s2, $s2, 8 
      bneqz $s1, Loop

问题拆解与优化步骤

你把addd移到bneqz后陷入困境,本质是没吃透延迟槽利用和无关指令填充停顿这两个核心技巧,下面一步步拆解:

1. 消除冗余STALL:用无关指令填充延迟槽

你的代码里插入了4个STALL,但其中3个可以用subi这类不依赖FP单元结果的整数指令替代——这些指令和FP运算完全无关,能直接占用原本空转的周期,避免性能浪费。

根据延迟规则:

  • ld到multd需要1周期延迟:ld执行后,必须等1周期才能启动multd,这里确实需要1个STALL(暂时没其他无关指令可填)
  • multd到addd需要3周期延迟:multd启动后,要等3周期才能用结果做addd,这期间刚好可以执行subi $s1和subi $s2,直接替换2个STALL

调整后单迭代代码:

Loop: ld $f0, 0($s1) 
      ld $f4, 0($s2)
      STALL  # 满足Load到FP ALU的1周期延迟
      multd $f0, $f0, $f4
      subi $s1, $s1, 8  # 填充停顿槽,替代STALL
      subi $s2, $s2, 8  # 填充停顿槽,替代STALL
      addd $f2, $f0, $f2 
      bneqz $s1, Loop

2. 利用分支延迟槽放置addd

单周期延迟分支的核心是:bneqz指令执行后,紧跟的延迟槽指令一定会被执行,无论分支是否跳转。这刚好可以用来放addd——因为addd依赖multd的结果,而延迟槽的执行时机刚好满足multd到addd的3周期延迟要求。

调整后的代码:

Loop: ld $f0, 0($s1) 
      ld $f4, 0($s2)
      STALL
      multd $f0, $f0, $f4
      subi $s1, $s1, 8 
      subi $s2, $s2, 8 
      bneqz $s1, Loop
      addd $f2, $f0, $f2  # 分支延迟槽指令,无条件执行

这里的逻辑完全成立:

  • 若$s1≠0(分支跳转):addd先执行(完成当前迭代的累加),再跳转到Loop开始下一轮,下一轮的ld会覆盖$f0,但此时addd已经用完$f0的值,无冲突
  • 若$s1=0(分支不跳转):addd执行最后一次累加,程序正常退出

这样又省掉了原本addd占用的独立周期,进一步压缩了迭代耗时。

3. 循环展开:彻底隐藏延迟(进阶优化)

如果要进一步提升性能,循环展开是关键——通过把多个迭代的指令重叠,让CPU在等待一个multd结果的同时,执行下一个迭代的ld和multd,完全消除STALL。

以展开2次为例(假设数组元素数为偶数):

Loop: ld $f0, 0($s1)    # 迭代1:加载第一个元素对
      ld $f4, 0($s2)
      ld $f6, -8($s1)   # 迭代2:加载第二个元素对
      ld $f8, -8($s2)
      multd $f0, $f0, $f4  # 迭代1:乘法
      multd $f6, $f6, $f8  # 迭代2:乘法(并行利用FP单元,无延迟冲突)
      subi $s1, $s1, 16    # 调整指针,对应2次迭代的步长
      subi $s2, $s2, 16
      addd $f2, $f2, $f0   # 累加迭代1的结果
      addd $f2, $f2, $f6   # 累加迭代2的结果
      bneqz $s1, Loop

展开后,原本需要的STALL被完全消除:前两个ld的延迟被后两个ld填充,multd的延迟被subi填充,整个执行流程没有空转周期,性能提升接近2倍。


内容的提问来源于stack exchange,提问作者0xMe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.09 00:05:15