Rustにおける単調時計を用いた高精度時間間隔の取得方法

システムクロックと単調クロックの比較

システムクロック(墙上时钟)は、私たちが日常的に使用する時間を指します。一方、単調クロック(monotonic clock)は特定の時点から経過した時間を計測し、絶対的な時間値は意味を持ちません。

オペレーティングシステムは、この2種類の時間をそれぞれ提供しています。システムクロックはUTC(協定世界時)で表現され、うるう秒をサポートせず、変更される可能性があります。また、石英時計などのハードウェアに依存しているため、温度変化などの要因による時間のずれが生じることがあります。通常、NTP(Network Time Protocol)を通じて同期されますが、これにより誤差が生じるため、高精度な時間計算には不十分です。

一方、単調クロックは時間が常に単調増加することを保証し、時間が逆戻りすることはありません。

Go言語での経過時間計算

Go言語のTime構造体には単調クロックが定義されています。


type Time struct {
    // wallとextは、壁時間の秒、ナノ秒、およびオプションの単調クロック読み取りをナノ秒単位でエンコードします。
    //
    // ビット位置の高い方から低い方へ、wallは1ビットのフラグ(hasMonotonic)、
    // 33ビットの秒フィールド、および30ビットの壁時間ナノ秒フィールドをエンコードします。
    // ナノ秒フィールドの範囲は[0, 999999999]です。
    // hasMonotonicビットが0の場合、33ビットのフィールドは0でなければならず、
    // 完全な符号付き64ビットの壁時間(紀元1年1月1日からの秒数)がextに格納されます。
    // hasMonotonicビットが1の場合、33ビットのフィールドは1885年1月1日からの33ビットの符号なし壁秒を保持し、
    // extはプロセス開始からのナノ秒単位の符号付き64ビット単調クロック読み取りを保持します。
    wall uint64
    ext  int64
    ...
}

wallがシステムクロックを、extが単調クロックを表しています。time.Subメソッドは、まずextを使用して計算を行い、extがない場合はUTCを使用して計算します。


func (t Time) Sub(u Time) Duration {
    if t.wall&u.wall&hasMonotonic != 0 {
        te := t.ext
        ue := u.ext
        d := Duration(te - ue)
        if d < 0 && te > ue {
            return maxDuration // t - u は正の範囲外
        }
        if d > 0 && te < ue {
            return minDuration // t - u は負の範囲外
        }
        return d
    }
    d := Duration(t.sec()-u.sec())*Second + Duration(t.nsec()-u.nsec())
    // オーバーフローまたはアンダーフローのチェック
    switch {
    case u.Add(d).Equal(t):
        return d // d は正しい
    case t.Before(u):
        return minDuration // t - u は負の範囲外
    default:
        return maxDuration // t - u は正の範囲外
    }
}

これは基本的に単調時間を使用して経過時間を計算します。


package main

import (
    "fmt"
    "time"
)

func main() {
    start := time.Now()
    time.Sleep(time.Second * 2)
    elapsed := time.Since(start)
    fmt.Printf("経過時間: %.2f 秒", elapsed.Seconds())
}

Rust言語での単調時間計算

Rustでは、std::Instant::now()を使用して単調時間を取得できます。


use std::time::{Duration, Instant};
use std::thread::sleep;

fn main() {
    let start = Instant::now();
    sleep(Duration::from_secs(2));
    println!("経過時間: {} 秒", start.elapsed().as_secs());
}

サードパーティライブラリにより、より高性能な実装も提供されています。Coarse Timeの実装であるcoarsetime::Instant::now()や、タイムスタンプカウンター(TSC)レジスタに基づいたtikv/minstantライブラリなどがあります。

タグ: rust Go 単調クロック システムクロック 高精度計測

6月30日 21:25 投稿