【问题标题】:Benchmark function with input that doesn't implement the Copy trait具有未实现 Copy 特征的输入的基准函数
【发布时间】:2021-12-01 00:42:16
【问题描述】:

我正在使用 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

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

【问题讨论】:

  • 真正的问题似乎出在基准哈希函数中。您不能将其修复为接受&amp;[&amp;str](并使用hashing::hash(&amp;tokens) 调用它)吗?为什么散列需要一个 vec ?
  • 为什么不克隆它?
  • @Stargateur 克隆参数解决了这个问题。谢谢!
  • @Stargateur 克隆向量的开销会影响基准测试吗?
  • @Stargateur @DenysSéguret 结果:与接受 &amp;[&amp;str] 相比,克隆函数的开销约为 50%。

标签: rust ownership rust-criterion


【解决方案1】:

正如@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 函数更改为接受&amp;[&amp;str] 可以避免克隆向量的约50% 开销。

【讨论】:

  • 或者,如果您可以更新hash 以获取切片,您可以move 闭包中的标记并将它们简单地借给散列函数。
【解决方案2】:

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

因此你应该使用iter_batched() 而不是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]                                                                                                     ```

【讨论】:

    猜你喜欢
    • 2016-05-29
    • 2022-10-07
    • 1970-01-01
    • 1970-01-01
    • 2015-06-13
    • 2019-12-08
    • 2015-09-19
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多