为测试目的计时函数调用的最简单方法是什么?
What is the easiest way to time a function call for testing purposes?
所以我对 Rust 还是有点陌生,但是来自 Python 我发现这种情况一般来说非常令人困惑。
我喜欢 Python 因为如果你想为一段代码或只是一个函数调用计时,这很容易:
print(timeit('a = "hee hee la le dah"; my_awesome_fn()', number = 1_000, globals=globals()))
然后只需调用 python script.py
或更好,但只需使用 IDE 中的绿色“运行”按钮即可调用脚本。但是我在 Rust 中找不到功能等价物。
我知道 Rust 生态系统中有一个称为基准测试的概念,一些像 criterion
这样的库就是为了这个目的而存在的。问题是我对高等数学和统计学一无所知(本质上可以把我当作一个无能的白痴)而且我怀疑我能否从这样的框架或工具中获益良多。
所以我只是好奇如何在 cargo 中使用 tests
来测试 Rust 中的代码块或更好的甚至是函数调用。
例如,假设我在 rust 中有类似的函数,我想多次调用它,然后检查性能如何变化等:
pub fn my_awesome_fn() {
trace!("getting ready to do something cool...");
std::thread::sleep(std::time::Duration::from_millis(500));
info!("finished!");
}
我怎样才能简单地在 Rust 中为这个函数 my_awesome_fn
计时?我想我正在寻找类似 python 中的 timeit
或类似的东西。理想情况下,应该直接使用并假设我对自己在做什么一无所知。我很好奇是否有我可以为此目的利用的现有库或框架。
免责声明:我从未使用过timeit
一个非常快速的解决方案是编写如下函数:
fn timeit<F: Fn() -> T, T>(f: F) -> T {
let start = SystemTime::now();
let result = f();
let end = SystemTime::now();
let duration = end.duration_since(start).unwrap();
println!("it took {} seconds", duration.as_secs());
result
}
您可以使用它来“包装”另一个函数调用:
fn main() {
let x = timeit(|| my_expensive_function());
}
但是,如果您试图了解一个函数所花费的时间以达到性能优化的目的,这种方法可能过于粗糙。
The problem is that I know nothing about advanced math and statistics
这可以说是 criterion
的主要优点之一,从某种意义上说,它“抽象了数学”。
它使用统计方法让您更好地了解基准测试 运行 之间的差异是否是“随机性”的产物,或者每个 运行 上的代码之间是否存在有意义的差异].
对于最终用户,它实质上是向您提供一份报告,说明“观察到重大变化”或“未观察到重大变化”。它的作用远不止于此,但要完全掌握其功能,可能值得阅读“假设检验”。
如果您可以使用 nightly Rust,您还可以使用 #[bench]
测试:
#![feature(test)]
extern crate test;
#[bench]
fn bench_my_func(b: &mut Bencher) {
b.iter(|| my_func(black_box(100));
}
您可以 运行 和 cargo bench
。这些比 criterion
更容易设置,但做的有趣的统计数据更少(即你必须自己做),但它们是一种非常“快速和肮脏”的方式来获得感觉运行你的代码时间。
警告一句,基准测试代码很难。您可能会对引擎盖下实际发生的事情感到惊讶,并且您可能会发现自己对错误的事情进行了基准测试。
常见的“陷阱”是:
rustc
一般可以识别“无用”代码,直接跳过计算。 black_box
函数可用于向优化器隐藏某些数据的含义,尽管它并非没有自己的开销
- 以类似的方式,LLVM 做了一些与多项式 for example 相关的稍微诡异的优化。您可能会发现您的函数调用被优化为 constant/simple 算术。在某些情况下,这很棒!您以 LLVM 可以将其简化为微不足道的方式编写函数。在其他情况下,您现在只是在 CPU 上对乘法指令进行基准测试,这不太可能是您想要的。用你最好的判断力
- 基准测试错误 - 有些东西比其他东西贵得多,对于具有 python 背景的人来说可能看起来很奇怪。例如,克隆
String
(即使是非常短的)可能比查找第一个字符慢 2-3 个数量级。考虑以下因素:
fn str_len(s: String) -> usize {
s.len()
}
#[bench]
fn bench_str_len(b: &mut Bencher) {
let s = String::from("hello");
b.iter(|| str_len(s.clone()));
}
因为String::clone
涉及堆分配,而s.len()
只是字段访问,会支配结果。相反,如果 str_len
取了 &str
,它将变得更具代表性(尽管这是一个人为的案例)。
TLDR 小心你的基准代码在做什么。 Rust Playground 的“查看程序集”工具(或 godbolt.org)是您的好帮手。您不需要成为装配专家,但它可以帮助您了解幕后发生的事情
所以我对 Rust 还是有点陌生,但是来自 Python 我发现这种情况一般来说非常令人困惑。
我喜欢 Python 因为如果你想为一段代码或只是一个函数调用计时,这很容易:
print(timeit('a = "hee hee la le dah"; my_awesome_fn()', number = 1_000, globals=globals()))
然后只需调用 python script.py
或更好,但只需使用 IDE 中的绿色“运行”按钮即可调用脚本。但是我在 Rust 中找不到功能等价物。
我知道 Rust 生态系统中有一个称为基准测试的概念,一些像 criterion
这样的库就是为了这个目的而存在的。问题是我对高等数学和统计学一无所知(本质上可以把我当作一个无能的白痴)而且我怀疑我能否从这样的框架或工具中获益良多。
所以我只是好奇如何在 cargo 中使用 tests
来测试 Rust 中的代码块或更好的甚至是函数调用。
例如,假设我在 rust 中有类似的函数,我想多次调用它,然后检查性能如何变化等:
pub fn my_awesome_fn() {
trace!("getting ready to do something cool...");
std::thread::sleep(std::time::Duration::from_millis(500));
info!("finished!");
}
我怎样才能简单地在 Rust 中为这个函数 my_awesome_fn
计时?我想我正在寻找类似 python 中的 timeit
或类似的东西。理想情况下,应该直接使用并假设我对自己在做什么一无所知。我很好奇是否有我可以为此目的利用的现有库或框架。
免责声明:我从未使用过timeit
一个非常快速的解决方案是编写如下函数:
fn timeit<F: Fn() -> T, T>(f: F) -> T {
let start = SystemTime::now();
let result = f();
let end = SystemTime::now();
let duration = end.duration_since(start).unwrap();
println!("it took {} seconds", duration.as_secs());
result
}
您可以使用它来“包装”另一个函数调用:
fn main() {
let x = timeit(|| my_expensive_function());
}
但是,如果您试图了解一个函数所花费的时间以达到性能优化的目的,这种方法可能过于粗糙。
The problem is that I know nothing about advanced math and statistics
这可以说是 criterion
的主要优点之一,从某种意义上说,它“抽象了数学”。
它使用统计方法让您更好地了解基准测试 运行 之间的差异是否是“随机性”的产物,或者每个 运行 上的代码之间是否存在有意义的差异].
对于最终用户,它实质上是向您提供一份报告,说明“观察到重大变化”或“未观察到重大变化”。它的作用远不止于此,但要完全掌握其功能,可能值得阅读“假设检验”。
如果您可以使用 nightly Rust,您还可以使用 #[bench]
测试:
#![feature(test)]
extern crate test;
#[bench]
fn bench_my_func(b: &mut Bencher) {
b.iter(|| my_func(black_box(100));
}
您可以 运行 和 cargo bench
。这些比 criterion
更容易设置,但做的有趣的统计数据更少(即你必须自己做),但它们是一种非常“快速和肮脏”的方式来获得感觉运行你的代码时间。
警告一句,基准测试代码很难。您可能会对引擎盖下实际发生的事情感到惊讶,并且您可能会发现自己对错误的事情进行了基准测试。
常见的“陷阱”是:
rustc
一般可以识别“无用”代码,直接跳过计算。black_box
函数可用于向优化器隐藏某些数据的含义,尽管它并非没有自己的开销- 以类似的方式,LLVM 做了一些与多项式 for example 相关的稍微诡异的优化。您可能会发现您的函数调用被优化为 constant/simple 算术。在某些情况下,这很棒!您以 LLVM 可以将其简化为微不足道的方式编写函数。在其他情况下,您现在只是在 CPU 上对乘法指令进行基准测试,这不太可能是您想要的。用你最好的判断力
- 基准测试错误 - 有些东西比其他东西贵得多,对于具有 python 背景的人来说可能看起来很奇怪。例如,克隆
String
(即使是非常短的)可能比查找第一个字符慢 2-3 个数量级。考虑以下因素:
fn str_len(s: String) -> usize {
s.len()
}
#[bench]
fn bench_str_len(b: &mut Bencher) {
let s = String::from("hello");
b.iter(|| str_len(s.clone()));
}
因为String::clone
涉及堆分配,而s.len()
只是字段访问,会支配结果。相反,如果 str_len
取了 &str
,它将变得更具代表性(尽管这是一个人为的案例)。
TLDR 小心你的基准代码在做什么。 Rust Playground 的“查看程序集”工具(或 godbolt.org)是您的好帮手。您不需要成为装配专家,但它可以帮助您了解幕后发生的事情