如何在 Rust 中为 Criterion 基准创建随机输入

问题描述

我正在尝试使用 Criterion 基准测试库对 annotate 例程进行基准测试。该例程正在检查 &[&str] 参数(二维方形字符串)并返回 Vec<String>,我怀疑其执行时间可能取决于参数的内容。因此,我想随机化单个工作负载的输入,在这种情况下,工作负载意味着字符串参数的宽度和高度(通常宽度 == 高度)。

我注意到 Criterion 有 iter_batchediter_batched_ref 方法,它们采用两个闭包而不是单个定时闭包、一个设置和一个例程(定时)。它们都是 FnMut 所以所有捕获的变量都是可变引用(据我所知)。因此,我尝试在设置中随机Vec<Vec<u8>>内容,而 Routine 仅调用我的 annotate 函数

fn plant_mines<R: Rng + ?Sized>(mfield: &mut Vec<Vec<u8>>,rng: &mut R) {
    mfield.into_iter().flatten().for_each(|x| *x = if rng.gen::<f32>() < mine_RATE {mine} else {EMPTY});
}

pub fn benchmark(c: &mut Criterion) {
    let mut rng = rand_pcg::Pcg64Mcg::seed_from_u64(RAND_SEED);
    let bench_params : [(usize,usize); 4] = [
        (3,3),(5,5),(8,8),(16,16)
    ];

    let dims2str = |d : (usize,usize)| { let (w,h) = d; format!("{:},{:}",w,h) };

    let mut group = c.benchmark_group("minefield Benchmark");
    for dims in bench_params.iter() {
        group.bench_with_input(BenchmarkId::from_parameter(dims2str(*dims)),dims,|b,(w,h)| {
            let mut mf : Vec<Vec<u8>> = (0..*h).map(|_| iter::repeat(EMPTY).take(*w).collect::<Vec<_>>()).collect();
            let mf_ref = mf.iter().map(|vu8| str::from_utf8(&vu8).unwrap()).collect::<Vec<&str>>();

            b.iter_batched(|| {
                plant_mines(&mut mf,&mut rng);
            },|_| annotate(&mf_ref),BatchSize::SmallInput);
        });
    }
    group.finish();
}

通常,Setup 函数应该为要使用的 Routine 生成输入,但在这种情况下,我省略了它们。 mf_ref 持有 Vec<Vec<u8>> 的参考向量,我不想在定时例程中创建/分配这个向量,否则,它会为基准产生更多的噪音。不幸的是,借阅检查员不高兴。

enter image description here

我尝试在 Setup 闭包中创建输入向量,但随后它无法将参考向量返回到定时例程。我试图了解 CellRefCell 中的任何一个是否可以解决问题,但我无法充分掌握它们以在此处应用(如果可能)。 Cell 是不可能的,因为 Vec 没有实现我理解的 copy 特性。

如何在仅对 annotate 函数进行基准测试并为每批样本使用随机输入的同时满足编译器的要求?

解决方法

您没有按预期使用批处理机制,这样做可以解决相互冲突的借用问题。您应该在设置闭包中创建 mf_ref 并返回它,而不是关闭 mf 的基准例程。这样,就不会共享对 mf 的可变引用。

b.iter_batched(|| {
    let mut mf : Vec<Vec<u8>> = (0..*h).map(|_| iter::repeat(EMPTY).take(*w).collect::<Vec<_>>()).collect();
    plant_mines(&mut mf,&mut rng);
    mf
},|mf| annotate(&mf),BatchSize::SmallInput);

请注意,这也摆脱了 mf_ref&str 的转换。除非字符串都是 &'static str(如果它们始终是对应于 MINE 或 EMPTY 的字符串,那么这可能会在此处实现(容易),这样您就可以只有两个相应的字符串文字);您需要重新设计您的 annotate,使其适用于拥有的结构或仅引用 &'static str 的结构。 (或者您可以将它放在 annotate 旁边的基准闭包中,然后您正在测量它。)

当然,您不应该仅仅为了基准测试而重构代码,但这可能会给您在其他地方提供灵活性,因为长期引用通常不方便。由于我不知道 annotate 是做什么的,或者 MINEEMPTY 的值是什么,我无法确切地建议您做什么。