benchmark_tests.md 3.9 KB
Newer Older
J
jackymao 已提交
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186
# 基准测试

基准测试通常是在开发的最后阶段进行的(虽然也有例外),用途是知道代码运行所花的时间,或提供代码中存在的性能缺陷的测试信息。

对所开发的程序执行基准测试有多种方法。

第一种简单的方法是使用 std::time::Instant 来测量程序的执行时间,虽然这并不能提供精确和完善的数据。

```rust
fn main() {
    use std::time::Instant;
    let now = Instant::now();

    // Code block to measure.
    {
        println!("test");
    }

    let elapsed = now.elapsed();
    println!("Elapsed: {:.2?}", elapsed);
}
```

第二种是使用 Rust 库,比较常见的库有 criterion 和 flamegraph


要做好一个基准测试及代码优化并不容易,需要考虑的因素很多:
- 不要过早的对代码进行优化和测试
- 在代码优化和安全性之间要平衡取舍
- 如果需要,对编译速度和编译文件大小之间要平衡取舍
- 使用优化编译命令对代码做基准测试,即编译的时候使用 rustc -O3 选项或 cargo build --release. 在使用 cargo bench 命令时它会自动打开优化
- 多次测试并进行统计。单次测试往往不够准确,测试用机的负载(操作系统,CPU,硬盘,缓存等)不同测试结果都会不同。
- 确保你的测试代码不会被后端优化掉,如 LLVM 会在后端进行各种级别的各种优化,或按照测试框架的库的说明来操作。
- 更多其他注意事项



对于 Fibonacci 序列算法,在以下的 Benchmark 中调用了四种算法

```rust

use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use rust_fibonacci::*;
use std::collections::HashMap;

fn bench_fibs(c: &mut Criterion) {
    let mut group = c.benchmark_group("Fibonacci");

    for i in [20, 21].iter() {
        group.bench_with_input(BenchmarkId::new("Standard", i), i, |b, i| {
            b.iter(|| fib_standard(*i))
        });

        group.bench_with_input(BenchmarkId::new("Recursion", i), i, |b, i| {
            b.iter(|| fib_recursive(*i))
        });

        group.bench_with_input(BenchmarkId::new("Memoization", i), i, |b, i| {
            b.iter(|| {
                let mut memo = HashMap::new();
                fib_memoization(*i, &mut memo);
            })
        });

        group.bench_with_input(BenchmarkId::new("Iterator", i), i, |b, i| {
            b.iter(|| {
                FibIterator::default().nth(*i).unwrap();
            })
        });
    }
    group.finish();
}

criterion_group!(benches, bench_fibs);
criterion_main!(benches);

```



问答:
    在如下的四种算法代码中,预期最慢的是:


## 答案

A

```rust

pub fn fib_recursive(n: usize) -> usize {
    match n {
        0 | 1 => 1,
        _ => fib_recursive(n-2) + fib_recursive(n-1),
    }
}

```


## 选项

### 

B


```rust

pub fn fib_standard(n: usize) -> usize {
    let mut a = 1;
    let mut b = 1;

    for _ in 1..n {
        let old = a;
        a = b;
        b += old;
    }

    b
}

```

###

C

```rust

use std::collections::HashMap;

pub fn fib_memoization(n: usize, memo: &mut HashMap<usize, usize>) -> usize {
    if let Some(v) = memo.get(&n) {
        return *v;
    }

    let v = match n {
        0 | 1 => 1,
        _ => fib_memoization(n-2, memo) + fib_memoization(n-1, memo),
    };

    memo.insert(n, v);
    v
}

```

###

D

```rust

pub struct FibIterator {
    a: usize,
    b: usize
}

impl Default for FibIterator {
    fn default() -> Self {
        FibIterator { a: 1, b: 1 }
    }
}

impl Iterator for FibIterator {
    type Item = usize;

    fn next(&mut self) -> Option<Self::Item> {
        let curr = self.a;
        self.a = self.b;
        self.b = curr + self.a;

        Some(curr)
    }
}

```


<!--
ref:
    https://www.umcconnell.net/posts/2021-03-13-fibonacci-rust/
    https://stackoverflow.com/questions/13322479/benchmarking-programs-in-rust
    https://zhuanlan.zhihu.com/p/451184900
-->