输入未实现 Copy 特征的基准函数

Benchmark function with input that doesn't implement the Copy trait

我是运行 Criterion 的基准测试,但我面临着输入未实现 Copy 特征的函数的问题。

例如,我为签名为 pub fn hash(vector: Vec<&str>) -> u64.

的函数设置了以下基准
pub fn criterion_benchmark(c: &mut Criterion) {
    let s: String = String::from("Hello World!");
    let tokens: Vec<&str> = hashing::tokenize(&s);
    c.bench_function(
        "hash",
        |b| b.iter(|| {
            hashing::hash(tokens)
        }),
    );
}

但是,与具有 Copy 特征的类型不同,编译器抛出以下所有权错误。

error[E0507]: cannot move out of `tokens`, a captured variable in an `FnMut` closure
  --> benches/benchmark.rs:17:34
   |
13 |     let tokens: Vec<&str> = hashing::tokenize(&s);
   |         ------ captured outer variable
...
17 |             hashing::hash(tokens)
   |                                  ^^^^^^ move occurs because `tokens` has type `Vec<&str>`, which does not implement the `Copy` trait

error[E0507]: cannot move out of `tokens`, a captured variable in an `FnMut` closure
  --> benches/benchmark.rs:16:20
   |
13 |     let tokens: Vec<&str> = hashing::tokenize(&s);
   |         ------ captured outer variable
...
16 |         |b| b.iter(|| {
   |                    ^^ move out of `tokens` occurs here
17 |             hashing::hash(tokens)
   |                                  ------
   |                                  |
   |                                  move occurs because `tokens` has type `Vec<&str>`, which does not implement the `Copy` trait
   |                                  move occurs due to use in closure

如何将不可复制的输入传递给基准函数而不 运行 出现所有权问题?

按照@Stargateur 的建议,克隆参数解决了所有权问题。

pub fn criterion_benchmark(c: &mut Criterion) {
    let s: String = String::from("Hello World!");
    let tokens: Vec<&str> = hashing::tokenize(&s);
    c.bench_function(
        "hash",
        |b| b.iter(|| {
            hashing::hash(tokens.clone())
        }),
    );
}

但是,正如@DenysSéguret 和@Masklinn 所提议的那样,将 hash 函数更改为接受 &[&str] 可避免克隆向量的 ~50% 开销。

克隆输入可能会导致基准测试结果出现严重错误。

因此你应该使用iter_batched() instead of iter()

use criterion::{black_box, criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion};
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};

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

fn criterion_benchmark(c: &mut Criterion) {
    let input_data: String = String::from("Hello World!");

    c.bench_function("bench_function", |bencher| {
        bencher.iter_batched(
            || init_data(&input_data),
            |input| {
                let x = benchmark_me(input);
                black_box(x);
            },
            BatchSize::SmallInput,
        );
    });

    c.bench_function("bench_function+clone", |bencher| {
        bencher.iter(|| {
            let x = benchmark_me(init_data(&input_data));
            black_box(x);
        });
    });
}

fn init_data(s: &str) -> Vec<&str> {
    // it's intentionally slower than a plain copy, to make the difference more visible!
    s.split_ascii_whitespace().collect()
}

fn benchmark_me(s: Vec<&str>) -> u64 {
    let mut hasher = DefaultHasher::new();
    s.hash(&mut hasher);
    hasher.finish()
}

结果:

bench_function                  time:   [99.520 ns 100.90 ns 102.23 ns]                           
bench_function+clone            time:   [210.41 ns 212.08 ns 213.77 ns]                                                                                                     ```