Rust中的基准程序

php*_*p-- 46 time benchmarking rust

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

Mic*_*ang 84

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));
    });
}
Run Code Online (Sandbox Code Playgroud)

对于命令,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
Run Code Online (Sandbox Code Playgroud)

链接:

  • 请注意,当前版本的Bencher标记为不稳定,不能从稳定的Rust中使用.目前仅适用于夜间版本. (7认同)
  • 显然,这种情况正在消失.我还在寻找我应该使用的东西. (5认同)
  • 只是更新,过了很长一段时间,#[bench] 仍然被认为不稳定,并且有人猜测它被贬低了。截至目前,我建议使用[criterion](https://github.com/bheisler/criterion.rs)。如果您想了解更多关于 #[bench] 的信息,我建议您从(我认为是的)开始进入兔子洞,[第一个跟踪此问题的线程](https://github.com/rust -lang/rust/issues/29553) (4认同)
  • 这本书的链接现在应该是所谓的“夜书”:https://doc.rust-lang.org/nightly/unstable-book/library-features/test.html (2认同)

Mar*_*oma 42

如果您只想为一段代码计时,则可以使用time包.然而,时间同时被弃用了.后续的箱子是chrono.

加入time = "*"你的Cargo.toml.

extern crate time;
use time::PreciseTime;
Run Code Online (Sandbox Code Playgroud)

在你的主要功能之前

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

完整的例子

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 = "*"
Run Code Online (Sandbox Code Playgroud)

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);
}
Run Code Online (Sandbox Code Playgroud)

  • 这有点令人困惑:第一段提到应该使用“chrono”,但看起来下面的代码仍然使用“time”? (5认同)
  • [`time`](https://github.com/rust-lang-deprecated/time) crate 现在显然已被弃用。 (2认同)
  • 在弃用了“时间”板条箱之后,[`chrono`](https://crates.io/crates/chrono)板条箱可能正是您要的东西。*不要将“时间”板条箱与标准库中的[`std :: time`](https://doc.rust-lang.org/std/time/index.html)模块混淆 (2认同)

Luk*_*odt 34

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


快速,方便:InstantDuration标准库中

要快速检查一段代码运行了多长时间,您可以使用std::time. 该模块相当小,但它适用于简单的时间测量。您应该使用Instant而不是SystemTime因为前者是一个单调递增的时钟,而后者不是。示例(游乐场):

use std::time::Instant;

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

标准的底层平台特定实现在文档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());
}
Run Code Online (Sandbox Code Playgroud)

执行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
Run Code Online (Sandbox Code Playgroud)

标准

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

有关如何使用 Criterion 的更多信息,请参阅“快速入门”部分


为什么编写基准测试很难

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

  • 编译优化rustc -O3cargo build --release。当您使用 执行基准测试时cargo bench,Cargo 将自动启用优化。这一步很重要,因为优化和未优化的 Rust 代码之间通常存在很大的性能差异。

  • 重复工作负载:只运行一次工作负载几乎总是没用的。有很多因素会影响您的时间安排:整体系统负载、操作系统执行的工作、CPU 节流、文件系统缓存等等。所以尽可能多地重复你的工作量。例如,Criterion 运行每个基准测试至少 5 秒(即使工作负载只需要几纳秒)。然后可以分析所有测量的时间,平均值和标准偏差是标准工具。

  • 确保您的基准测试没有被完全删除:基准测试本质上是非常人为的。通常,不会检查工作负载的结果,因为您只想测量持续时间。然而,这意味着一个好的优化器可以删除你的整个基准,因为它没有副作用(除了时间的流逝)。所以要欺骗优化器,你必须以某种方式使用你的结果值,这样你的工作量就不能被删除。一个简单的方法是打印结果。更好的解决方案是类似black_box. 这个函数基本上对LLVM隐藏了一个值,因为 LLVM 无法知道这个值会发生什么。什么都没有发生,但 LLVM 不知道。这就是我想说的。

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

  • 注意常量值:与上面的点类似,如果您在基准测试中指定常量值,优化器可能会专门为该值生成代码。在极端情况下,您的整个工作负载可能会被常数折叠成一个常数,这意味着您的基准测试毫无用处。传递所有常量值black_box以避免 LLVM 过于激进地优化。

  • 注意测量开销:测量持续时间本身需要时间。这通常只有几十纳秒,但会影响您的测量时间。因此,对于速度超过几十纳秒的所有工作负载,您不应单独测量每个执行时间。您可以执行 100 次工作负载并测量所有 100 次执行所用的时间。将其除以 100 即可得出平均单次时间。上面提到的基准测试框架也使用了这个技巧。Criterion 还提供了一些方法来测量具有副作用的非常短的工作负载(例如改变某些东西)。

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


ide*_*n42 18

对于时序测试,您可以使用 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();
}
Run Code Online (Sandbox Code Playgroud)

当然,如果你经常这样做,你会想要推广转换,或者使用为它提供实用程序的包,或者在你自己的函数中包装Instant,Duration这样它们就可以用不那么冗长的方式编写.

  • 由于`std :: time :: SystemTime`不能保证单调递增,因此它不是基准测试的最佳选择.对于这个用例,最好使用单调[`std :: time :: Instant`](https://doc.rust-lang.org/std/time/struct.Instant.html). (4认同)
  • 请注意,您还可以通过其“Debug”实现简单地输出“Duration”。示例:`println!("{:.2?}", elapsed)`。 (3认同)
  • 为什么 `my_function_to_measure();` 在它自己的块中(包含在 `{ }` 中)?有必要吗? (3认同)
  • 它和“time”板条箱的“precise_time_ns”一样精确吗? (2认同)

Kev*_*ntu 10

您可以尝试使用时间箱来计算程序中的各个组件.

  • [[time`](https://github.com/rust-lang-deprecated/time)板条箱现在显然已被弃用。 (6认同)
  • `time` 箱子现已在 0.2.0 版本中发布,并且不再弃用。然而,据我所知,在进行基准测试时,它比“std::time”没有任何优势。因此,我宁愿将“此答案已过时”警告保留在顶部。下面有很多更好的答案。 (2认同)
  • @LukasKalbertodt 我是新发布的时间箱的作者。使用它进行基准测试的理由为零,特别是考虑到内置(尽管是夜间)API。 (2认同)

Pau*_*fer 9

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

~$ time sleep 4

real    0m4.002s
user    0m0.000s
sys     0m0.000s
Run Code Online (Sandbox Code Playgroud)

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


小智 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 report,并valgrind依赖于程序的调试信息的可用性。它可能不起作用。