首先,让我们证明这确实发生了。
从以下代码开始:
pub fn sum(start: i32, end: i32) -> i32 {
let mut result = 0;
for i in start..end {
result += i;
}
return result;
}
在发布版编译中,我们得到:
; playground::sum
; Function Attrs: nounwind nonlazybind readnone uwtable
define i32 @_ZN10playground3sum17h41f12649b0533596E(i32 %start1, i32 %end) {
start:
%0 = icmp slt i32 %start1, %end
br i1 %0, label %bb5.preheader, label %bb6
bb5.preheader: ; preds = %start
%1 = xor i32 %start1, -1
%2 = add i32 %1, %end
%3 = add i32 %start1, 1
%4 = mul i32 %2, %3
%5 = zext i32 %2 to i33
%6 = add i32 %end, -2
%7 = sub i32 %6, %start1
%8 = zext i32 %7 to i33
%9 = mul i33 %5, %8
%10 = lshr i33 %9, 1
%11 = trunc i33 %10 to i32
%12 = add i32 %4, %start1
%13 = add i32 %12, %11
br label %bb6
bb6: ; preds = %bb5.preheader, %start
%result.0.lcssa = phi i32 [ 0, %start ], [ %13, %bb5.preheader ]
ret i32 %result.0.lcssa
}
我们可以确实观察到没有循环了。
因此,我们验证了Bandy和Orendorff的声明。
关于这是如何发生的,我的理解是这全部发生在LLVM中的
ScalarEvolution.cpp。不幸的是,那个文件有12,000多行,所以导航起来有点复杂;不过,头注释暗示我们应该在正确的位置,并指向了它所使用的论文,其中提到了优化循环和闭式函数
1:
根据Krister Walfridsson的
这篇博客文章,它构建了
递归链,可用于获得每个归纳变量的闭式公式。
这是完全推理和完全硬编码之间的中间点:
- 使用模式匹配来构建递归链,因此LLVM可能无法识别表示某个计算的所有方法。
- 可以优化各种各样的公式,而不仅仅是三角形求和。
该文章还指出,优化可能会导致代码悲观:如果“优化”代码需要比循环内部更多的操作,则少量迭代可能更快。
1n *(n + 1)/ 2
是计算[0,n]
范围内数字总和的闭式函数。
rust
标签。 - Kyle Strand