编译器(特别是rustc)真的能简化三角形求和以避免循环吗?怎么用?

编译器(特别是rustc)真的能简化三角形求和以避免循环吗?怎么用?,rust,compiler-construction,compiler-optimization,llvm-codegen,Rust,Compiler Construction,Compiler Optimization,Llvm Codegen,在布兰迪和奥伦多夫的《编程生锈》第322页上,有这样一项声明: …Rust…认识到有一种更简单的方法可以将数字从1相加到n:总和始终等于n*(n+1)/2 这当然是一个相当有名的等价物,但是编译器如何识别它呢?我猜这是在LLVM优化过程中,但LLVM是以某种方式从第一原理推导出等价物,还是它只是有一些可以简化为算术运算的“公共循环计算”?首先,让我们证明这确实发生了 从以下代码开始: pub fn sum(开始:i32,结束:i32)->i32{ 设mut result=0; 因为我在开始…结束

在布兰迪和奥伦多夫的《编程生锈》第322页上,有这样一项声明:

…Rust…认识到有一种更简单的方法可以将数字从1相加到
n
:总和始终等于
n*(n+1)/2


这当然是一个相当有名的等价物,但是编译器如何识别它呢?我猜这是在LLVM优化过程中,但LLVM是以某种方式从第一原理推导出等价物,还是它只是有一些可以简化为算术运算的“公共循环计算”?

首先,让我们证明这确实发生了

从以下代码开始:

pub fn sum(开始:i32,结束:i32)->i32{
设mut result=0;
因为我在开始…结束{
结果+=i;
}
返回结果;
}
我们得到:

在那里我们确实可以观察到不再存在循环

因此,我们验证了Bandy和Orendorff的说法


至于这是如何发生的,我的理解是这一切都发生在LLVM中。不幸的是,这个文件有12000多行,所以导航有点复杂;尽管如此,总评还是暗示我们应该在正确的位置,并指出它使用的论文提到了优化循环和闭式函数1:

//===----------------------------------------------------------------------===//
//
//本分析中使用的技术有一些很好的参考。
//
//复发链——一种加速评估的方法
//闭函数的性质
//奥拉夫·巴赫曼、保罗·S·王、尤金·齐马
//
//关于递归链的计算性质
//尤金诉齐马
//
//循环优化循环链的符号求值
//罗伯特·范恩格伦
//
//优化编译器的高效符号分析
//罗伯特·范恩格伦
//
//使用递归代数链进行数据依赖性测试和分析
//诱导变量代换
//博士论文,约翰尼·伯奇
//
//===----------------------------------------------------------------------===//
根据Krister Walfridsson的说法,它建立了循环链,可用于获得每个归纳变量的封闭式公式

这是完全推理和完全硬编码之间的中点:

  • 模式匹配用于构建递归链,因此LLVM可能无法识别表示特定计算的所有方式
  • 可以优化各种各样的公式,而不仅仅是三角和
文章还指出,优化可能会使代码变得悲观:如果“优化”的代码与循环的内部相比需要更多的操作,那么少量的迭代可能会更快


1
n*(n+1)/2
是一个封闭形式的函数,用于计算
[0,n]

中的数字之和,Haskell在LLVM启动之前具有类似的效果。但据我所知,Rust编译器可以完全不同地完成它。(在rustc源代码中没有一个文件夹的名字突然出现在我的脑海中,称为“这就是要查看的文件夹”)@JeremyList啊,那么这可能比我想象的更具体。我添加了
rust
标记。这种等价实际上是少数其他等价物的组合——(部分)循环展开、常数折叠、算术运算的重新排序——所有这些都是编译器(实际上是LLVM的优化器)能够做的。我不确定你是否会考虑“从首要原则出发”,但我不会觉得太奇怪,因为它能自己计算出等价性。我可以看到一个“愚蠢”的优化器计算任何纯函数,其参数在编译时是已知的;然而,推导这样的公式有点复杂。推导封闭形式需要一些符号推理。@trentcl:多亏了,我修改了我的答案。LLVM似乎走了一段路:模式匹配建立了循环链,然后可以“简化”为闭合形式,允许它将优化应用于大量循环。
; 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
}