编译器(特别是rustc)是否真的可以简化三角求和以避免循环?怎么样?

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了编译器(特别是rustc)是否真的可以简化三角求和以避免循环?怎么样?相关的知识,希望对你有一定的参考价值。

在Blandy和Orendorff撰写的Rust编程的第322页是这种说法:

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

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

答案

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

从这段代码开始:

pub fn sum(start: i32, end: i32) -> i32 {
    let mut result = 0;
    for i in start..end {
        result += i;
    }
    return result;
}

compiling in Release,我们得到:

; 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多行的monstrutruosity,所以导航它有点复杂;仍然,主管评论暗示我们应该在正确的位置,并指向它使用的文件,提到优化循环和封闭形式的功能1:

 //===----------------------------------------------------------------------===//
 //
 // There are several good references for the techniques used in this analysis.
 //
 //  Chains of recurrences -- a method to expedite the evaluation
 //  of closed-form functions
 //  Olaf Bachmann, Paul S. Wang, Eugene V. Zima
 //
 //  On computational properties of chains of recurrences
 //  Eugene V. Zima
 //
 //  Symbolic Evaluation of Chains of Recurrences for Loop Optimization
 //  Robert A. van Engelen
 //
 //  Efficient Symbolic Analysis for Optimizing Compilers
 //  Robert A. van Engelen
 //
 //  Using the chains of recurrences algebra for data dependence testing and
 //  induction variable substitution
 //  MS Thesis, Johnie Birch
 //
 //===----------------------------------------------------------------------===//

根据Kazter Walfridsson的this blog article,它建立了复发链,可以用来获得每个归纳变量的封闭形式公式。

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

  • 模式匹配用于构建重复链,因此LLVM可能无法识别表达某种计算的所有方式。
  • 可以优化多种公式,而不仅仅是三角形和。

文章还指出,优化可能最终使代码变得悲观:如果“优化”代码与循环的内部主体相比需要更多的操作,则少量迭代可以更快。

1 n * (n+1) / 2是用于计算[0, n]中数字总和的闭合函数。

以上是关于编译器(特别是rustc)是否真的可以简化三角求和以避免循环?怎么样?的主要内容,如果未能解决你的问题,请参考以下文章

Rust 编译器结合 Cranelift ,显著缩短调试构建时间

任务 'rustc' 已溢出其堆栈

OpenGL 着色器有时可以编译,有时不能

Rust 编译器探索使用 PGO

由于 rustc 没有“-fPIC”标志,如何将 rust 代码编译为 PIC 二进制文件?

使用 Swift 4 用非素数向下三角求和