如何在Rust中对程序进行基准测试?

j0pj023g  于 2023-01-02  发布在  其他
关注(0)|答案(9)|浏览(273)

是否可以在Rust中对程序进行基准测试?如果可以,如何进行?例如,我如何获得程序的执行时间(秒)?

llmtgqce

llmtgqce1#

两年后,可能值得注意的是(为了帮助任何未来的Rust程序员),现在有了作为测试套件一部分的Rust代码基准测试工具。
(From(下面的指南链接)使用#[bench]属性,可以使用标准的Rust工具在代码中对方法进行基准测试。

extern crate test;
use test::Bencher;

#[bench]
fn bench_xor_1000_ints(b: &mut Bencher) {
    b.iter(|| {
        // Use `test::black_box` to prevent compiler optimizations from disregarding
        // Unused values
        test::black_box(range(0u, 1000).fold(0, |old, new| old ^ new));
    });
}

对于命令cargo bench,输出如下所示:

running 1 test
test bench_xor_1000_ints ... bench:       375 ns/iter (+/- 148)

test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured

链接:

l5tcr1uw

l5tcr1uw2#

要在不添加第三方依赖项的情况下测量时间,可以使用std::time::Instant

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

    // Code block to measure.
    {
        my_function_to_measure();
    }

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

tyu7yeag3#

如果你只是想给一段代码计时,你可以使用time crate.time meanwhile deprecated,接下来的crate是chrono
time = "*"添加到您的Cargo.toml

extern crate time;
use time::PreciseTime;

在您的主要功能之前

let start = PreciseTime::now();
// whatever you want to do
let end = PreciseTime::now();
println!("{} seconds for whatever you did.", start.to(end));

完整示例

货物. toml

[package]
name = "hello_world" # the name of the package
version = "0.0.1"    # the current version, obeying semver
authors = [ "you@example.com" ]
[[bin]]
name = "rust"
path = "rust.rs"
[dependencies]
rand = "*" # Or a specific version
time = "*"

rust.rs

extern crate rand;
extern crate time;

use rand::Rng;
use time::PreciseTime;

fn main() {
    // Creates an array of 10000000 random integers in the range 0 - 1000000000
    //let mut array: [i32; 10000000] = [0; 10000000];
    let n = 10000000;
    let mut array = Vec::new();

    // Fill the array
    let mut rng = rand::thread_rng();
    for _ in 0..n {
        //array[i] = rng.gen::<i32>();
        array.push(rng.gen::<i32>());
    }

    // Sort
    let start = PreciseTime::now();
    array.sort();
    let end = PreciseTime::now();

    println!("{} seconds for sorting {} integers.", start.to(end), n);
}
rjjhvcjd

rjjhvcjd4#

有几种方法可以对你的Rust程序进行基准测试。对于大多数真实的的基准测试,你应该使用一个合适的基准测试框架,因为它们有助于解决一些容易出错的事情(包括统计分析)。请同时阅读最底部的“为什么编写基准测试很难”部分!

简单快捷:标准库中的InstantDuration

为了快速检查一段代码运行了多长时间,你可以使用std::time中的类型。这个模块相当小,但是对于简单的时间测量来说是不错的。你应该使用Instant而不是SystemTime,因为前者是一个单调递增的时钟,而后者不是。例如(Playground):

use std::time::Instant;

let before = Instant::now();
workload();
println!("Elapsed time: {:.2?}", before.elapsed());

std的Instant的底层特定于平台的实现在文档中指定。目前(可能永远),您可以假设它使用平台所能提供的最佳精度(或非常接近的精度)。根据我的测量和经验,这通常约为20 ns。
如果std::time没有为您的情况提供足够的特性,您可以看看chrono,然而,对于测量持续时间,您不太可能需要外部机箱。

使用基准测试框架

使用框架通常是一个好主意,因为它们试图防止您犯常见的错误。

Rust内置的基准测试框架(仅限夜间)

Rust有一个方便的内置基准测试功能,遗憾的是截至2019-07仍然不稳定,你必须给你的函数添加#[bench]属性,并让它接受一个&mut test::Bencher参数:

#![feature(test)]

extern crate test;
use test::Bencher;

#[bench]
fn bench_workload(b: &mut Bencher) {
    b.iter(|| workload());
}

执行cargo bench将打印:

running 1 test
test bench_workload ... bench:      78,534 ns/iter (+/- 3,606)

test result: ok. 0 passed; 0 failed; 0 ignored; 1 measured; 0 filtered out

标准

crate criterion是一个运行在stable上的框架,但它比内置的解决方案要复杂一些,它可以进行更复杂的统计分析,提供更丰富的API,生成更多的信息,甚至可以自动生成图表。
有关如何使用Criterion的更多信息,请参见“快速入门”部分。

为什么编写基准测试很难

编写基准测试时有很多陷阱。一个错误就可能使你的基准测试结果毫无意义。下面是一些重要但经常被遗忘的要点:

*使用优化进行编译rustc -O3cargo build --release。当您使用cargo bench执行基准测试时,Cargo会自动启用优化。这一步很重要,因为优化和未优化的Rust代码之间通常存在很大的性能差异。
*重复工作负荷:只运行一次工作负载几乎总是无用的。2有很多因素会影响你的时间安排:总体系统负载、操作系统执行的操作、CPU限制、文件系统缓存等。因此,尽可能频繁地重复您的工作负载。例如,Criterion运行每个基准测试至少5秒(即使工作负载只需要几纳秒)。然后,可以使用标准工具分析所有测量的时间。
*确保您的基准没有被完全删除:基准测试本质上是非常人为的。通常情况下,不会检查工作负载的结果,因为您只想测量持续时间。然而,这意味着一个好的优化器可以删除整个基准测试,因为它没有副作用(好吧,除了时间的流逝)。所以为了欺骗优化器,你必须设法使用你的结果值,这样你的工作量才不会被删除。一个简单的方法是打印结果。一个更好的解决方案是类似black_box的东西。这个函数基本上隐藏了LLVM中的一个值,因为LLVM不知道这个值会发生什么,什么也不会发生,但是LLVM不知道,这就是关键所在。

好的基准测试框架在几种情况下使用块框。例如,给iter方法(内置和Criterion Bencher)的闭包可以返回一个值。该值自动传递到black_box

*注意常量值:与上述情况类似,如果在基准测试中指定常量值,优化器可能会专门为该值生成代码。在极端情况下,整个工作量可能会被常量合并为单个常量,这意味着基准测试毫无用处。请通过black_box传递所有常量值,以避免LLVM优化过于激进。
*注意测量开销:测量持续时间本身需要时间。2通常只有几十纳秒,但是会影响你测量的时间。3所以对于所有比几十纳秒快的工作负载,你不应该单独测量每个执行时间。2你可以执行你的工作负载100次,然后测量所有100次执行所花费的时间。3将其除以100就得到了平均单次执行时间。上面提到的基准测试框架也使用了这个技巧。Criterion也有一些方法来测量有副作用的非常短的工作负载(比如变异一些东西)。

*许多其他事情:遗憾的是,我不能在这里列出所有的困难。2如果你想写严肃的基准测试,请阅读更多的在线资源。

vd8tlhqk

vd8tlhqk5#

此答案已过时!time机箱与std::time相比,在基准测试方面没有任何优势。有关最新信息,请参阅下面的答案。

您可以尝试使用time crate为程序中的各个组件计时。

wqsoz72f

wqsoz72f6#

无论使用何种实现语言,了解程序执行时间的一种快速方法是在命令行上运行time prog。例如:

~$ time sleep 4

real    0m4.002s
user    0m0.000s
sys     0m0.000s

最有趣的度量通常是user,它度量程序完成的实际工作量,而不管系统中发生了什么(sleep是一个非常无聊的程序,无法进行基准测试)。real度量实际经过的时间,sys度量操作系统代表程序完成的工作量。

6ss1mwsb

6ss1mwsb7#

目前,没有与以下任何Linux函数的接口:

  • 第一个月
  • getrusage
  • times(联机帮助页:man 2 times

测量Linux上Rust程序的CPU时间和热点的可用方法有:

  • /usr/bin/time program
  • perf stat program
  • perf record --freq 100000 program; perf report
  • valgrind --tool=callgrind program; kcachegrind callgrind.out.*

perf reportvalgrind的输出取决于程序中调试信息的可用性。它可能不工作。

nwwlzxa7

nwwlzxa78#

我为此创建了一个小板条箱(measure_time),它记录或打印直到范围结束的时间。

#[macro_use]
extern crate measure_time;
fn main() {
    print_time!("measure function");
    do_stuff();
}
9njqaruj

9njqaruj9#

度量执行时间的另一个解决方案是创建一个自定义类型,例如一个struct,并为它实现Drop trait。
例如:

struct Elapsed(&'static str, std::time::SystemTime);

impl Drop for Elapsed {
    fn drop(&mut self) {
        println!(
            "operation {} finished for {} ms",
            self.0,
            self.1.elapsed().unwrap_or_default().as_millis()
        );
    }
}

impl Elapsed {
    pub fn start(op: &'static str) -> Elapsed {
        let now = std::time::SystemTime::now();

        Elapsed(op, now)
    }
}

把它用在某个功能上:

fn some_heavy_work() {
  let _exec_time = Elapsed::start("some_heavy_work_fn");
  
  // Here's some code. 
}

当函数结束时,将调用_exec_time的drop方法并打印消息。

相关问题