5.8 KiB
Lab6:并行与循环优化
1. 本实验定位
Lab6 的目标是在 Lab5 基本标量优化之后,面向“循环密集型代码”继续优化。
本实验重点不是再补语义覆盖,而是提升中后端对循环结构的处理能力,为性能优化打基础。
核心方向:
- 循环识别与规范化
- 循环相关优化(如不变代码外提、强度削弱等)
- 可并行循环分析与并行化改造(可选,按课程要求推进)
2. 先看这一点:循环优化依赖分析结果
循环优化通常依赖一组基础分析:
- CFG 与支配关系
- 循环层次信息(LoopInfo)
- def-use/use-def 与副作用信息(保证变换合法)
如果这些基础信息不稳定,循环优化很容易“优化错程序”。
因此 Lab6 建议先把分析链路和验证手段搭好,再做具体变换。
3. Lab6 要求
需要同学完成:
- 在现有 IR 上识别循环结构(至少能区分循环头、循环体、回边)。
- 实现有效的循环优化,并保证语义不变。
- 将循环优化并入
PassManager,与 Lab5 的优化流程协同工作。 - 对优化前后结果做回归验证,并给出性能或代码规模对比(至少一种指标)。
- 可选:尝试实现可并行循环识别与并行化改造(按课程要求)。
4. 当前代码框架
-
IR 与分析
include/ir/IR.hsrc/ir/analysis/DominatorTree.cppsrc/ir/analysis/LoopInfo.cpp
-
IR 优化
src/ir/passes/
-
入口与验证
src/main.cpp
5. 需要修改的文件
-
核心文件
src/ir/analysis/LoopInfo.cpp(完善循环分析)src/ir/passes/PassManager.cpp(接入新的循环优化 pass)- 新增循环优化 pass 文件(建议放在
src/ir/passes/下)
-
视实现需要可能修改
src/ir/analysis/DominatorTree.cpp(若分析信息不足)include/ir/IR.h、src/ir/Instruction.cpp(若需要补充指令属性)src/ir/IRPrinter.cpp(调试输出增强)
6. 可选优化方向
- 循环不变代码外提(LICM)
- 归纳变量简化与强度削弱
- 循环展开(小规模、受控展开)
- 循环分裂
- 简单并行化识别(无跨迭代写后读/写后写冲突时标记可并行) ...
7. 简要优化原理
7.1 循环不变量外提(Loop Invariant Code Motion)
原理:
将循环中“每次迭代结果都不变”的表达式移动到循环外执行。若某表达式不依赖循环内变化的值,并且其操作数在循环内不被改写,则它是循环不变量。
作用:
减少循环体内重复计算,降低迭代开销。
实现思路:
先识别循环结构,再判断循环体中哪些表达式对所有迭代恒定。把这些表达式外提到循环前置块(或等价安全位置),循环体改用外提结果。
7.2 强度削弱(Strength Reduction)
原理:
将高开销运算替换为等价低开销运算。典型场景是把循环中的乘法/除法改写为增量更新(加减)。
作用:
降低每次迭代的算术成本,提高整体执行效率。
实现思路:
识别归纳变量与其线性相关表达式,判断是否可改写为递增/递减更新。引入辅助变量后,用加减替代高成本运算并保持语义一致。
7.3 循环展开(Loop Unrolling)
原理:
一次迭代中执行多份循环体副本。
作用:
减少控制指令比例,提高指令级并行机会,也有利于后续向量化或流水线优化。
实现思路:
选择展开因子,复制循环体并调整步长。若总迭代次数不能整除展开因子,需要保留余数迭代处理路径以保证结果正确。
7.4 循环分裂(Loop Fission)
原理:
把一个包含多类语句的循环拆成多个循环,每个循环执行原循环的一部分语句。
作用:
降低单个循环体复杂度,改善数据局部性,并为并行化/向量化创造条件。
实现思路:
先做数据依赖分析。若若干语句间不存在阻碍重排的依赖,可将其拆分到不同循环中,同时维持必要执行顺序保证语义不变。
7.5 循环并行化(Loop Parallelization)
原理:
把循环不同迭代同时执行,以利用多核并行能力。前提是迭代间不存在破坏语义的数据依赖。
作用:
在数据规模较大时可显著缩短运行时间,尤其适用于数值计算与批量数据处理。
实现思路:
先判定迭代间读写依赖。若可并行,则将迭代划分为任务并分发到线程/处理单元;结束后进行同步与归并,确保结果正确。
8. 推荐实验流程
- 跑通循环分析。
- 选择循环优化实现
- 接入
PassManager。 - 用样例验证正确性。
- 统计优化前后差异。
9. 构建与验证
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j "$(nproc)"
9.1 功能回归
./scripts/verify_ir.sh test/test_case/simple_add.sy test/test_result/ir --run
./scripts/verify_asm.sh test/test_case/simple_add.sy test/test_result/asm --run
--run 模式下脚本会自动读取同名 .in,并将程序输出与退出码和同名 .out 比对。
完成 Lab6 后,不能只检查 simple_add 这类单个样例,而应对 test/test_case 下全部测试用例逐个回归;如有需要,也可以自行编写批量测试脚本统一执行。
9.2 优化效果对比(示例)
# 对比优化前后 IR/汇编输出(按你实现的开关或日志方式执行)
./build/bin/compiler --emit-ir test/test_case/simple_add.sy
./build/bin/compiler --emit-asm test/test_case/simple_add.sy
这里的 simple_add 只用于展示如何观察单个样例的输出差异;实际评估优化效果时,仍应结合更多测试用例,必要时覆盖全部测试集。