# 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` 只用于展示如何观察单个样例的输出差异;实际评估优化效果时,仍应结合更多测试用例,必要时覆盖全部测试集。