Rust中的基准程序

时间:2012-11-10 13:31:25

标签: time benchmarking rust

如何在Rust中对程序进行基准测试?例如,如何在几秒钟内获得程序的执行时间?

9 个答案:

答案 0 :(得分:81)

2年之后(为了帮助任何偶然发现这个页面的Rust程序员)可能值得注意的是,现在有工具可以将Rust代码作为测试套件的一部分进行基准测试。

(从下面的指南链接)使用#[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

链接:

答案 1 :(得分:39)

如果您只想为一段代码计时,可以使用time包。但是time meanwhile deprecated。后续的箱子是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));

完整示例

Cargo.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);
}

答案 2 :(得分:13)

对于时序测试,您可以使用std::time::Instant

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

    {
        my_function_to_measure();
    }

    let elapsed = now.elapsed();
    let sec = (elapsed.as_secs() as f64) + (elapsed.subsec_nanos() as f64 / 1000_000_000.0);
    println!("Seconds: {}", sec);
}

fn main() {
    my_function();
}

当然,如果您经常这样做,您可能想要推广转换,或者使用为此提供实用程序的包,或者在您自己的函数中包装InstantDuration以便可以编写它们以一种不那么冗长的方式。

答案 3 :(得分:11)

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

答案 4 :(得分:7)

无论实现语言如何,找出程序执行时间的快捷方法是在命令行上运行time prog。例如:

~$ time sleep 4

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

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

答案 5 :(得分:5)

目前,以下任何Linux功能都没有接口:

  • clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts)
  • 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的输出取决于程序中调试信息的可用性。它可能不起作用。

答案 6 :(得分:4)

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


方便快捷:标准库中的InstantDuration

要快速检查一段代码的运行时间,可以使用std::time中的类型。该模块相当少,但是对于简单的时间测量来说很好。您应该使用Instant而不是SystemTime,因为前者是单调递增的时钟,而后者不是。示例(Playground):

use std::time::Instant;

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

不幸的是,文档中并未指定std Instant的精度,但是在所有主要操作系统上,它使用平台可以提供的最佳精度(通常约为20ns)。

如果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

标准

周转箱criterion是一个可以稳定运行的框架,但比内置解决方案要复杂一些。它执行更复杂的统计分析,提供更丰富的API,产生更多信息,甚至可以自动生成图。

有关如何使用“准则”的更多信息,请参见the "Quickstart" section


为什么编写基准比较困难

编写基准测试时有很多陷阱。一个错误会使基准测试结果毫无意义。以下是常见错误的列表:

  • 进行优化编译rustc -O3cargo build --release。当您使用cargo bench执行基准测试时,Cargo将自动启用优化。此步骤很重要,因为优化和未优化的Rust代码之间通常会有很大的性能差异。
  • 重复工作量:仅运行一次工作量几乎总是无济于事。有很多因素会影响您的时间安排:总体系统负载,操作系统正在执行的工作,CPU节流,文件系统缓存等等。因此,请尽可能多地重复您的工作量。例如,Criterion运行每个基准测试至少5秒钟(即使工作负载仅花费几纳秒的时间)。然后可以分析所有测量的时间,以平均值和标准差为标准工具。
  • 确保您的基准没有完全删除:基准本质上是非常人为的。通常,不检查工作负载的结果,因为您只想测量持续时间。但是,这意味着一个好的优化程序可以消除整个基准,因为它没有副作用(当然,除了时间的流逝之外)。因此,要诱骗优化器,您必须以某种方式使用结果值,以便无法删除工作负载。一种简单的方法是打印结果。更好的解决方案是类似black_box。该函数基本上从LLVM隐藏了一个值,因为LLVM无法知道该值会发生什么。什么也没发生,但是LLVM不知道。这就是重点。

    好的基准测试框架在某些情况下会使用阻止框。例如,为iter方法提供的闭包(对于内置Bencher和Criterion black_box而言)都可以返回一个值。该值将自动传递到black_box中。

  • 当心常量值:与上述要点类似,如果您在基准测试中指定常量值,则优化器可能会为该值专门生成代码。在极端的情况下,您的整个工作量可能会被固定为一个常数,这意味着基准测试毫无用处。通过{{1}}传递所有常量值,以免LLVM过于积极地进行优化。
  • 当心测量开销:测量持续时间本身需要花费时间。通常只有几十纳秒,但是会影响您的测量时间。因此,对于所有快于几十纳秒的工作负载,您不应分别测量每个执行时间。您可以执行100次工作负载,并测量全部100次执行所花费的时间。将其除以100可得出平均单次访问时间。上面提到的基准测试框架也使用此技巧。 Criterion还提供了一些方法来测量非常短的工作负载,这些工作负载会产生副作用(例如变异某些东西)。
  • 许多其他事情:很遗憾,我无法在这里列出所有困难。如果您想编写严格的基准测试,请阅读更多在线资源。

答案 7 :(得分:2)

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

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

答案 8 :(得分:0)

测量执行时间的另一种解决方案是创建自定义类型,例如结构体并为其实现 Drop 特征。

例如:

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 方法并打印消息。