はじめに
Rust でメソッドの実行時間を測定したいと思ったので、調べてみました。小ネタですが Rustその2 Advent Calendar 2020 24日目 アドベントカレンダーが空いていたので、埋めておきます。
環境
とりあえず、手持ちで一番安定している環境ということで。
- MacBook Pro (Retina, 15-inch, Mid 2015)
- Processor: 2.8 GHz Quad-Core Intel Core i7-4980HQ
- Memory: 16 GB 1600 MHz DDR3
- macOS 10.15.7 (19H114)
- rustc 1.48.0 (7eac88abb 2020-11-16)
crate std
測定対象のメソッドを呼ぶ前と呼んだ後で std::time::Instant::now()
を呼びだし、duration_since()
メソッドで duration を求めるのが簡単で分かりやすい方法だと思います。
std::time::Instant
のドキュメントによれば、次のようなシステムコールを呼んでいるそうです。私が今のところ使ったことがあるプラットフォームだけ抽出しておきます。
プラットフォーム | システムコール |
---|---|
UNIX | clock_gettime(CLOCK_MONOTONIC) |
macOS | mach_absolute_time |
WASI | __wasi_clock_time_get(CLOCKID_MONOTONIC) |
Windows | QueryPerformanceCounter |
crate quanta
最近の x86 系の CPU であれば、CPU 内に TSC カウンタというリアルタイムクロックで駆動されるカウンタがありますので、それを直接呼び出すものです。CPU 内のレジスタを読み出すだけなので、システムコールを呼び出すよりは格段にオーバーヘッドが少ないのではと思ってしまいます。オーバーヘッドcrate quanta
のドキュメントによれば、TSC カウンタの読み出しオーバーヘッドは 11ns 程度らしいので、意外とオーバーヘッドがあるのねぇ、と思ってしまいます。
やってみよう!
とりあえず、カウンタを1万回呼び出し、その平均値を求めましょう。
use std::time::Instant;
use quanta::Clock;
fn main() {
let mut clock = Clock::new();
const N:u32 = 1_000_000;
let istart = Instant::now();
let mut istop = istart;
for _ in 1..N {
istop = Instant::now();
}
println!("std::time:Instant::now() overhead = {:?}",
istop.duration_since(istart));
let start = clock.now();
let mut stop = start;
for _ in 1..1_000_000 {
stop = clock.now();
}
println!("quanta::clock::now() overhead = {:?}",
stop.duration_since(start));
}
$ cargo r --quiet --release
std::time::Instant::duration 15.614537ms
quanta::Instant::duration 13.580445ms
私の環境だと、何度やってもこの程度でした。1,000,000回呼び出しているので、std と quanta のオーバーヘッドはそれぞれ 15.6ns, 13.6ns ぐらいのようです。
おわりに
crate quanta
を使った方が 2.0ns ほどオーバーヘッドが小さいようです。
今回は、こんなところで。