Rust能力养成之(17)基准测试

编程入门 行业动态 更新时间:2024-10-10 17:32:57

Rust能力养成之(17)<a href=https://www.elefans.com/category/jswz/34/1764864.html style=基准测试"/>

Rust能力养成之(17)基准测试

 

前言

 

当业务需求发生变化,或者程序需要更为强劲的执行效果时,首先要做的是找出程序中速度较慢的地方在哪里,比如何判断瓶颈在什么?一般而言,可以通过在不同的预期范围或输入上检测程序的各个相关部分来进行判断,而这就是为对代码所进行的基准测试(benchmark)。基准测试通常是在开发的最后阶段进行的(虽然也有例外),用途是提供代码中存在的性能缺陷的测试信息。

对所开发的程序执行基准测试有多种方法。第一种简单的方法是使用Unix tool time来测量更改后程序的执行时间,但这并不能提供精确的微观视角。而Rust为我们提供了一个内置的微基准测试框架。所谓微观基准测试,指的是可以用来代码的各个部分进行独立的基准测试,并且不受外部因素的影响。然而,这同时也意味着我们不应该仅仅依赖于微观基准,因为现实世界的结果可能会产生或造成偏差。因此,在微观基准测试之后,通常还会对代码进行概要分析和宏观基准测试。尽管如此,微基准测试依然是提高代码性能的起点,毕竟单个部分对程序的总体运行时间贡献还是很大的。

在本篇中,我们将讨论如何使用Rust所提供的用来执行微基准测试的内置工具。可喜的是,Rust从开发的初始阶段就降低了编写基准代码的门槛,而不是把这个部分作为不得已为之的手段。运行基准测试的方式与运行测试的方式类似,使用的是cargo bench命令。

 

 

内置微型基准测试工具

(Built-in micro-benchmark harness)

 

Rust的内置基准测试框架通过在多个迭代中运行代码来度量代码的性能,并报告相关操作的平均耗时,此事有两部分组成:

  • 在函数上添加的#[bench]注释,这将函数标记为基准测试。

  • 内部编译器将libtest打包为一个Bencher类型,基准函数使用这个类型在多个迭代中运行相同的基准代码。该类型是编译器内部类型,驻留在测试crate之下。

现在,我们将编写并运行一个简单的基准测试。先运行Cargo new——lib bench_example来创建一个新的Cargo项目。现在没必要修改Cargo.toml文件, src/lib.rs的内容如下:

// bench_example/src/lib.rs
#![feature(test)]extern crate test;
use test::Bencher;
pub fn do_nothing_slowly() {print!(".");for _ in 1..10_000_000 {};}
pub fn do_nothing_fast() { }
#[bench]fn bench_nothing_slowly(b: &mut Bencher) {b.iter(|| do_nothing_slowly());}
#[bench]fn bench_nothing_fast(b: &mut Bencher) {b.iter(|| do_nothing_fast());}

需要注意一下,我们必须使用external crate声明来指定内部crate测试,以及#[feature(test)]属性。编译器内部的crate需要如此的extern声明。在未来的编译器版本中,这可能不需要了,到时候可以像普通crate一样使用。

 

键入cargo bench,看一下结果,报错如下:

 

不幸的是,基准测试是一个不稳定的特性,因此我们必须使用nightly compiler来进行这些测试。幸运的是,有了rustup,在Rust编译器的不同发布频道之间移动是很容易的。首先,我们将通过运行rustup update来确保nightly compiler编译器已经安装。然后,在bench_example目录中,通过运行rustup override set来覆盖该目录的默认工具链。那么完成之后,现在,运行cargo bench,可以给出以下输出:

这些是每次迭代的纳秒数,括号内的数字显示了每次运行之间的变化。可见,slower implementation的实现相当缓慢,并且在运行时间上也有变化(如大的+/-变化所示)。

在我们用#[bench]标记的函数中,iter的参数是一个没有参数的闭包(不知道到现在,读者还记得这个名词么?)。如果闭包有参数,将在||中,这实际上意味着向iter传递了一个可以重复运行基准测试的函数。我们在函数中打印一个点,这样Rust就不会优化空循环。如果println!()不存在,那么编译器将把循环优化为无操作,我们将得到错误的结果。有一些方法可以解决这个问题,可以通过使用测试模块中的black_box函数来实现。然而,使用它并不能保证优化器不会优化您的代码。现在,我们也有了第三方解决方案来运行稳定的Rust基准测试。

 

 

稳定的Rust基准测试

(Benchmarking on stable Rust)

 

Rust提供的内置基准测试框架是不稳定的,幸运的是,由语言社区开发的一些基准测试工具可以在稳定的Rust上工作。我们将在这里探讨的一个很受欢迎的crate是criteria -rs。该crate在设计上易于使用,同时提供基准代码的详细信息,还保持上一次运行的状态,报告每次运行的性能回归(如果有的话)。Criterion.rs生成的统计报告信息比内置基准测试框架提供的内容要多,而且还使用gnuplot生成图表,保证用户能够理解。

为了演示如何使用这个crate,我们使用命令cargo new criterion_demo--lib创建一个新的crate。要使用这个包,需要将其添加到Cargo.toml中的dev-dependencies部分,作为一个依赖项:

[dev-dependencies]criterion = "0.1"
[[bench]]name = "fibonacci"harness = false

我们还添加了一个名为[[bench]]的新部分,向cargo表明我们有一个名为fibonacci的新基准测试,并且不使用内置的基准测试套件(harness = false),因为使用的是criterion crate的测试套件。

现在,在src / lib.rs中,我们有一个计算第n个斐波那契数列的函数的快版本和慢版本(初始值n0 = 0和n1 = 1):

// criterion_demo/src/lib.rs
pub fn slow_fibonacci(nth: usize) -> u64 {if nth <= 1 {return nth as u64;       } else {return slow_fibonacci(nth - 1) + slow_fibonacci(nth - 2);    }}
pub fn fast_fibonacci(nth: usize) -> u64 {let mut a = 0;let mut b = 1;let mut c = 0;for _ in 1..nth {c = a + b;        a = b;        b = c;    }c}

fast_fibonacci是自底向上的迭代解决方案,以获得第n个斐波那契数,而slow_fibonacci版本是慢递归版本。现在,criteria -rs要求我们将基准测试放在benches/目录中,而这个目录在crate根目录中。在benches/目录中,我们还创建了一个名为fibonacci.rs的文件,与Cargo.toml中[[bench]]内的名称匹配。其有以下内容,​​​​​​​

// criterion_demo/benches/fibonacci.rs
#[macro_use]extern crate criterion;
extern crate criterion_demo;
use criterion_demo::{slow_fibonacci, fast_fibonacci};
use criterion::Criterion;
fn fibonacci_benchmark(c: &mut Criterion) {    c.bench_function("fibonacci 8", |b| b.iter(|| fast_fibonacci(8)));}
criterion_group!(fib_bench, fibonacci_benchmark);criterion_main!(fib_bench);

在上述代码中有很多信息!我们首先声明了所需的crate,并导入了需要进行基准测试的fibonacci函数(fast_fibonacci和slow_fibonacci)。此外,在extern crate标准之上有一个#[macro_use]属性,这意味着要来使用来自crate的任何宏,这里需要选择使用这个属性,因为这些属性在默认情况下是不公开的,这一点类似于use语句。

现在,criterion有了基准组的概念,这些基准组可以保存相关的基准代码。以此为基础,我们创建了一个名为fibonacci_benchmark的函数,然后将其传递给criterion_group!宏。这将给这个基准测试组分配一个fib_bench名称。随后,fibonacci_benchmark函数接受一个对criterion对象的可变引用,而该对象保存了基准测试运行的状态。这里是一个名为bench_function的方法,将传入的基准测试代码运行在一个给定名称(上图斐波那契8)的闭包上。然后,我们需要创建的主要基准测试工具:用一个main函数通过criterion_main !,在传入基准组fib_bench之前,来生成代码的主要功能并跑一遍。现在,是时候来运行cargo bench了。我们得到以下输出:

可见报告的信息还是比较充分的,这里面体现的fast_fibonacci的效果。后续篇章,还对基准测试多多安排几个实践内容,此刻感到有点懵的读者,暂且不用捉急。

 

结语

 

下一篇,我们测试一个逻辑门的模拟器

 

主要参考和建议读者进一步阅读的文献

1.Rust编程之道,2019, 张汉东

2.The Complete Rust Programming Reference Guide,2019, Rahul Sharma,Vesa Kaihlavirta,Claus Matzinger

3.Hands-On Data Structures and Algorithms with Rust,2018,Claus Matzinger

4.Beginning Rust ,2018,Carlo Milanesi

5.Rust Cookbook,2017,Vigneshwer Dhinakaran

更多推荐

Rust能力养成之(17)基准测试

本文发布于:2024-02-13 09:43:36,感谢您对本站的认可!
本文链接:https://www.elefans.com/category/jswz/34/1758192.html
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。
本文标签:基准   能力   测试   Rust

发布评论

评论列表 (有 0 条评论)
草根站长

>www.elefans.com

编程频道|电子爱好者 - 技术资讯及电子产品介绍!