我很高兴地宣布,中间层 IR (MIR) 常量传播 passes 已在 Rust nightly 版本中默认开启,最终将成为 Rust 1.41!
什么是常量传播?
常量传播是一种优化,编译器识别可以在编译时运行的代码,对其进行求值,并将原始代码替换为结果。
例如
const X: u32 = 2;
let y = X + X;
编译器可以识别出 X
的值在编译时已知,而不是在运行时对 X + X
进行求值,并将其替换为正确的值,结果为
const X: u32 = 2;
let y = 4;
此优化是机会性的,即使常量未声明为常量,它也会自动识别
let a = 2 + 2; // optimizes to 4
let b = ; // optimizes to 3
let c = .y; // optimizes to 42
传播到控制流中
常量传播 passes 还会处理传播到控制流中。例如
const Foo: = Some;
let x = match Foo ;
变为
const Foo: = Some;
let x = 12;
这对于检查数学运算(在 debug
模式下默认启用)非常有用,它在每次操作后引入额外的控制流
let x = 2 + 4 * 6;
实际上在启用溢出检查后是这样运作的
let = CheckedMultiply;
assert!;
let = CheckedAdd;
assert!;
let x = _temp1;
这增加了相当多的控制流!常量传播在编译时对数学运算进行求值,将其简化为
let _tmp0 = 24;
assert!;
let _tmp1 = 26;
assert!;
let x = 26;
并进一步简化为仅
let x = 26;
编译器性能
正如你可能猜到的,减少 Rust 编译器处理的控制流数量对编译时间有积极影响。我们在 debug 和 release 模式下的各种测试用例中看到了 2-10% 的改进。尽管 LLVM 有它自己的常量传播 passes,但我们看到了改进,因为我们的 passes 在 MIR 仍然是泛型时进行操作。泛型函数的具体实例化越多,此优化的收益就越大。
我们怀疑 Rust 编译器生成的冗长 LLVM IR 在很大程度上导致了漫长的编译时间已有一段时间了。通过实现这样的优化,我们相信通过生成更好的 LLVM IR,有很大的潜力可以缩短编译时间。如果你想参与到 MIR 优化工作组中,可以到我们的 Zulip 频道 打个招呼!