为什么我的递归Fibonacci实现与迭代实现相比如此之慢?

Jan*_*ner 4 recursion performance benchmarking rust

我创建了以下简单的Fibonacci实现:

#![feature(test)]
extern crate test;

pub fn fibonacci_recursive(n: u32) -> u32 {
    match n {
        0 => 0,
        1 => 1,
        _ => fibonacci_recursive(n - 1) + fibonacci_recursive(n - 2)
    }
}

pub fn fibonacci_imperative(n: u32) -> u32 {
    match n {
        0 => 0,
        1 => 1,
        _ => {
            let mut penultimate;
            let mut last = 1;
            let mut fib = 0;
            for _ in 0..n {
                penultimate = last;
                last = fib;
                fib = penultimate + last;
            }
            fib
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

我创建它们试试cargo bench,所以我写了以下基准:

#[cfg(test)]
mod tests {
    use super::*;
    use test::Bencher;

    #[bench]
    fn bench_fibonacci_recursive(b: &mut Bencher) {
        b.iter(|| {
            let n = test::black_box(20);
            fibonacci_recursive(n)
        });
    }

    #[bench]
    fn bench_fibonacci_imperative(b: &mut Bencher) {
        b.iter(|| {
            let n = test::black_box(20);
            fibonacci_imperative(n)
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

我知道递归实现通常比命令式实现慢,特别是因为Rust不支持尾递归优化(这种实现无论如何都不能使用).但我没想到近2 000次以下差异:

running 2 tests
test tests::bench_fibonacci_imperative ... bench:          15 ns/iter (+/- 3)
test tests::bench_fibonacci_recursive  ... bench:      28,435 ns/iter (+/- 1,114)
Run Code Online (Sandbox Code Playgroud)

我在Windows和Ubuntu上使用最新的Rust nightly编译器(rustc 1.25.0-nightly)运行它并获得了类似的结果.

这种速度差异是否正常?我写了一些"错误的"吗?或者我的基准有缺陷吗?

Fre*_*ios 9

正如Shepmaster所说,你应该使用累加器来保持先前计算的fib(n - 1),fib(n - 2)否则你会继续计算相同的值:

pub fn fibonacci_recursive(n: u32) -> u32 {
    fn inner(n: u32, penultimate: u32, last: u32) -> u32 {
        match n {
            0 => penultimate,
            1 => last,
            _ => inner(n - 1, last, penultimate + last),
        }
    }
    inner(n, 0, 1)
}

fn main() {
    assert_eq!(fibonacci_recursive(0), 0);
    assert_eq!(fibonacci_recursive(1), 1);
    assert_eq!(fibonacci_recursive(2), 1);
    assert_eq!(fibonacci_recursive(20), 6765);
}
Run Code Online (Sandbox Code Playgroud)

last相当于fib(n - 1).
penultimate相当于fib(n - 2).

  • @Boiethios:的确如此.基本上TCO可归结为:递归调用是函数执行的*last*事物.在这里,`inner`的最后一行被称为`inner`,所以它有资格......虽然它在C++或Rust中并不总是那么明显,因为析构函数是最后执行的,即使它们没有出现在代码中,因此可能会阻止TCO.在这里,没有析构函数,一切都很好:) (3认同)
  • @JanNilsFerner看起来尾部调用优化正在起作用;)我在*某些情况下读取(不保证)LLVM可以执行TCO. (2认同)