Files
nudt-compiler-cpp/doc/Lab6-并行与循环优化.md
2026-03-13 21:37:37 +08:00

79 lines
5.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Lab6并行与循环优化
## 1. 本实验定位
Lab6 的重点是在 Lab4 基本标量优化之后,继续围绕循环结构开展更进一步的性能优化。本实验不再以补齐语义覆盖为主,而是把重点放在循环识别、循环变换以及可并行循环分析等问题上,为进一步提升最终生成代码的性能打基础。
## 2. Lab6 要求
本实验需要完成的事情包括:在现有 IR 上识别循环结构,能够区分循环头、循环体、前置块、退出块与回边等部分;实现有效的循环优化,并保证变换前后语义一致;将这些优化接入 `PassManager`,使其能够与 Lab4 的优化流程协同工作;最后通过回归测试和性能或代码规模对比,验证优化结果的正确性与收益。若希望进一步提升性能,也可以继续尝试可并行循环识别与并行化改造。
## 3. 相关文件
以下文件与本实验内容相关,建议优先阅读。
- `include/ir/IR.h`
- `src/ir/analysis/DominatorTree.cpp`
- `src/ir/analysis/LoopInfo.cpp`
- `src/ir/passes/PassManager.cpp`
## 4. 当前基础与前置准备
循环优化通常依赖一组相对稳定的基础分析,包括 CFG 与支配关系、循环层次信息(`LoopInfo`),以及 def-use/use-def 和副作用信息。只有这些基础信息足够稳定,后续的循环变换才不容易“优化错程序”。因此,在正式实现具体优化之前,建议先把分析链路与验证手段理顺。
## 5. 可实现的优化方向与实现提示
本实验可以选择的方向包括循环不变代码外提、归纳变量简化与强度削弱、循环展开、循环分裂,以及简单的并行化识别。如果你的实现还需要围绕当前框架补充其他循环相关优化,也可以按需扩展。
### 5.1 循环不变量外提Loop Invariant Code Motion
循环不变代码外提的核心,是把循环中每次迭代结果都不变的表达式移动到循环外执行。若某个表达式不依赖循环内变化的值,并且其操作数在循环体内不被改写,那么它就具备外提条件。这样做的直接收益,是减少循环体内的重复计算,降低迭代开销。实现时,通常需要先识别循环结构,再判断哪些表达式对所有迭代都恒定,然后把它们外提到循环前置块或其他等价安全位置。
### 5.2 强度削弱Strength Reduction
强度削弱的思路,是把高开销运算替换为等价的低开销运算。循环中的典型场景,是把乘法、除法等操作改写为递增或递减更新。这样可以降低每次迭代的算术成本,提高整体执行效率。实现时,通常需要先识别归纳变量以及与其线性相关的表达式,再判断是否可以通过引入辅助变量,用加减更新替代高成本运算。
### 5.3 循环展开Loop Unrolling
循环展开的做法,是在一次迭代中执行多份循环体副本,以减少控制指令比例,并提升指令级并行机会。它也常常能为后续向量化或流水线优化创造条件。实现时,需要选择合适的展开因子,复制循环体并调整步长;如果总迭代次数不能整除展开因子,还需要保留余数迭代路径以保证结果正确。
### 5.4 循环分裂Loop Fission
循环分裂是把一个包含多类语句的循环拆成多个循环,每个循环只执行原循环中的一部分语句。这样做通常有助于降低单个循环体的复杂度,改善数据局部性,并为并行化或向量化提供更好的前提。实现时,一般需要先做数据依赖分析;只有在若干语句之间不存在阻碍重排的依赖时,才适合将其拆分到不同循环中。
### 5.5 循环并行化Loop Parallelization
循环并行化的目标,是让不同迭代可以并发执行,以利用多核并行能力。它成立的前提,是迭代间不存在破坏语义的数据依赖。若分析结果表明循环可以并行,就可以进一步考虑任务划分、执行与归并,从而继续提升整体性能。不过,这一部分通常也有一定难度,对依赖分析、任务划分和执行正确性的要求都更高,因此更适合作为在前面优化基础上继续深入的方向。
## 6. 推荐实验流程
比较自然的推进顺序是:先跑通循环分析,再选择一种或几种循环优化逐步实现,然后接入 `PassManager`,最后结合测试与输出对比检查优化结果是否正确、是否带来预期收益。
## 7. 构建与验证
```bash
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build -j "$(nproc)"
```
### 7.1 功能回归
```bash
./scripts/verify_ir.sh test/test_case/functional/simple_add.sy test/test_result/function/ir --run
./scripts/verify_asm.sh test/test_case/functional/simple_add.sy test/test_result/function/asm --run
```
`--run` 模式下脚本会自动读取同名 `.in`,并将程序输出与退出码和同名 `.out` 比对。
完成 Lab6 后,不能只检查 `simple_add` 这类单个样例,而应对 `test/test_case` 下全部测试用例逐个回归;如有需要,也可以自行编写批量测试脚本统一执行。
### 7.2 优化效果对比(示例)
```bash
# 对比优化前后 IR/汇编输出(按你实现的开关或日志方式执行)
./build/bin/compiler --emit-ir test/test_case/functional/simple_add.sy
./build/bin/compiler --emit-asm test/test_case/functional/simple_add.sy
```
这里的 `simple_add` 只用于展示如何观察单个样例的输出差异;实际评估优化效果时,仍应结合更多测试用例,必要时覆盖全部测试集。