システムクロックと単調クロックの比較
システムクロック(墙上时钟)は、私たちが日常的に使用する時間を指します。一方、単調クロック(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ライブラリなどがあります。