LoginSignup
14

More than 5 years have passed since last update.

プログラミング言語における時刻取得APIまわりの話

Last updated at Posted at 2017-01-23

あらすじ: 各言語の clock_gettime(2) の引数の扱いを調査していたら、このような奇っ怪な文章ができあがっていた。そういう事情であるので、Windows関係の記述がありません。後々追加するかもしれません。

clock_gettime

clock_gettime(2) はSingle Unix Specification v2によって規格化されている時間取得のためのAPIである。 clock_gettime(2) には clock_id に渡す定数によって時刻取得のセマンティクスや精度が変わってくる。

一例をLinuxのmanから引用すると、

- CLOCK_REALTIME
実時間を計測するシステム全体で一意な時間。 このクロックを設定するには適切な特権が必要である。 このクロックは、システム時間の不連続な変化 (例えば、システム管理者が システム時間を手動で変更した場合など) や adjtime や NTP が行う 段階的な調整の影響を受ける。
CLOCK_REALTIME_COARSE (Linux 2.6.32 以降; Linux 特有)
高速だが精度が低い CLOCK_REALTIME。速度が非常に必要で、かつ高精度のタイムスタンプが不要な場合に使用するとよい。
CLOCK_MONOTONIC
設定することができないクロックで、ある開始時点からの単調増加の時間で 表現されるクロック (開始時点がどの時点となるかは規定されていない)。 この時計は、システム時間の不連続な変化 (例えば、システム管理者がシステ ム時間を手動で変更した場合など) の影響を受けないが、 adjtime(3) や NTP が行う段階的な調整の影響を受ける。
CLOCK_MONOTONIC_COARSE (Linux 2.6.32 以降; Linux 特有)
高速だが精度が低い CLOCK_MONOTONIC。速度が非常に必要で、かつ高精度のタイムスタンプが不要な場合に使用するとよい。
CLOCK_MONOTONIC_RAW (Linux 2.6.28 以降; Linux 特有)
CLOCK_MONOTONIC と同様だが、NTP による調整や adjtime(3) が行う 段階的な調整の影響を受けない、ハードウェアによる生の時刻へのアクセス ができる。
CLOCK_BOOTTIME (Linux 2.6.39 以降; Linux 固有)
CLOCK_MONOTONIC と同じだが、システムがサスペンドされている時間も含まれる点が異なる。 これを使うと、アプリケーションはサスペンド状態も扱える "monotonic" なクロックを得ることができる。 しかも、 CLOCK_REALTIME における複雑な処理を行う必要もなくなる。 CLOCK_REALTIME では、 settimeofday(2) を使って時刻を変更した場合、時刻に不連続な変化が発生するからだ。

とある。この定数はOSによってまちまちである。またsierraのように clock_gettime_nsec_np という特殊なAPIを追加しているものもある。

こちらのブログは実装の引用箇所が参考になった。 http://d.hatena.ne.jp/yohei-a/20140913/1410628229

Ruby

  • CRuby 2.4.0

Process.clock_gettime

Rubyは直接 clock_gettime(2) を呼ぶAPIを提供している。Process.clock_gettime の引数に clock_id を指定する。

指定できるのはprocess.cによると以下の定数。ちゃんとどのバージョンで追加されたのかも書いており大変すばらしい。SolarisのCLOCK_HIGHRESに関しては指定がなかった。またDragonflyBSDも少なくとも直近のバージョンではFreeBSDと同じオプションを使用できるはずであるが言及がなかった。

 *  [CLOCK_REALTIME] SUSv2 to 4, Linux 2.5.63, FreeBSD 3.0, NetBSD 2.0, OpenBSD 2.1, macOS 10.12
 *  [CLOCK_MONOTONIC] SUSv3 to 4, Linux 2.5.63, FreeBSD 3.0, NetBSD 2.0, OpenBSD 3.4, macOS 10.12
 *  [CLOCK_PROCESS_CPUTIME_ID] SUSv3 to 4, Linux 2.5.63, OpenBSD 5.4, macOS 10.12
 *  [CLOCK_THREAD_CPUTIME_ID] SUSv3 to 4, Linux 2.5.63, FreeBSD 7.1, OpenBSD 5.4, macOS 10.12
 *  [CLOCK_VIRTUAL] FreeBSD 3.0, OpenBSD 2.1
 *  [CLOCK_PROF] FreeBSD 3.0, OpenBSD 2.1
 *  [CLOCK_REALTIME_FAST] FreeBSD 8.1
 *  [CLOCK_REALTIME_PRECISE] FreeBSD 8.1
 *  [CLOCK_REALTIME_COARSE] Linux 2.6.32
 *  [CLOCK_REALTIME_ALARM] Linux 3.0
 *  [CLOCK_MONOTONIC_FAST] FreeBSD 8.1
 *  [CLOCK_MONOTONIC_PRECISE] FreeBSD 8.1
 *  [CLOCK_MONOTONIC_COARSE] Linux 2.6.32
 *  [CLOCK_MONOTONIC_RAW] Linux 2.6.28, macOS 10.12
 *  [CLOCK_MONOTONIC_RAW_APPROX] macOS 10.12
 *  [CLOCK_BOOTTIME] Linux 2.6.39
 *  [CLOCK_BOOTTIME_ALARM] Linux 3.0
 *  [CLOCK_UPTIME] FreeBSD 7.0, OpenBSD 5.5
 *  [CLOCK_UPTIME_FAST] FreeBSD 8.1
 *  [CLOCK_UPTIME_RAW] macOS 10.12
 *  [CLOCK_UPTIME_RAW_APPROX] macOS 10.12
 *  [CLOCK_UPTIME_PRECISE] FreeBSD 8.1
 *  [CLOCK_SECOND] FreeBSD 8.1

Time.now

最もよく使われるであろう Time.now というか Time#initialize で呼び出される現在時刻はclock_gettimeが定義されていれば CLOCK_REALTIME 固定で呼び出されている。ない場合はgettimeofday(macOSなど)を使っている。

ドキュメントには書かれていないが、内部でclock_gettime/gettimeofdayが失敗した場合は rb_sys_fail で例外を出す。

Python3 (3.3以降)

  • CPython 3.6.0

time.clock_gettime

Pythonも3.3以降ではRubyと同様に time.clock_gettime(clock_id) で直接 clock_gettime(2) を呼ぶことができる。ただしclock_idに渡すことのできるものはアンドキュメンテッドである上に、どうやら CLOCK_MONOTONIC_COARSE などの定数は渡すことができない(3.6.0現在)。そもそもPythonは内部で CLOCK_MONOTONIC_COARSE を使ったり外から渡すAPIを提供していないようだ。

pytime.cをみるとpymonotonic関数で CLOCK_MONOTONIC 、pygettimeofday関数で CLOCK_REALTIME 固定でそれぞれ使われている。pymonotonicではmacOSの場合 mach_absolute_time を、Solarisの場合 CLOCK_HIGHRES を渡している。

time.time

最もよく使われるであろうtime.time()pygetimeofday 関数を内部で呼んでおり、これは time.clock_gettime(CLOCK_REALTIME) と等価となる。RubyのTime.now同様にfallback先としてgettimeofday(2)を使っている。

システムコール呼び出しに失敗した場合は内部でNULLを返しているがよくわからん。

pep418

Python3.3でtimeモジュールに新設されたclock系の提案PEPはこれ https://www.python.org/dev/peps/pep-0418/

Perl5

  • Perl 5.24

本体に付属?しているTime::HiResモジュールがデファクトのようだ。

clock_gettime

clock_gettimeCLOCK_MONOTONICCLOCK_REALTIME を渡せる。Ruby,Python同様、clock_gettimeはOS側で定義されていない場合は生えないのでgettimofday(2)にfallbackする仕組みはない。現時点で最新のmacOS Sierraで新設されたAPIには対応してなかったが、FreeBSDの CLOCK_MONOTONIC_FAST などは定数として定義されている。

time

time関数は内部ではgettimeofday(2)を呼んでいる。gettimeofday(2)の呼び出しに失敗した場合に-1.0を返すようだ。いったいこれはどういうことだろうと考えたところ、システムコールの失敗ステータスをfloatとして表現しているのだろうと思い至った。Perlには直和型も直積型も例外もないのでこのように返り値の意味が混在した表現にするよりないのだろう。

めんどくさくなったので雑になった。

C++

  • gcc6.2

STLにあるstd::chrono(libstdc++-v3/src/c++11/chrono.cc) をみる。

std::chrono::system_clock::now

  1. clock_gettime(2)があれば CLOCK_REALTIME を使う
  2. なければ gettimeofday(2) を使う
  3. それもなければ time(2) を使う

std::chrono::steady_clock::now

  1. clock_gettime(2)があれば CLOCK_MONOTONIC を使う
  2. なければ std::chrono::system_clock を使って差分を求める

となっている。

Go

  • Go 1.8rc2

多くのプラットフォーム(Linux,FreeBSD,OpenBSD)では、各アーキテクチャごとに now の実装に CLOCK_REALTIMEnanotime の実装に CLOCK_MONOTONIC を使っている。NetBSD,Solarisに関してはnanotimeの実装にも CLOCK_REALTIME を用いているが、これはよくわからない。Plan9はなんかよくわからない。

静的リンクなバイナリという事情があるためか、macOSでは事情が込み入っている。しかしこちらもよくわからず。参照されていたURLだけはっておく。 https://opensource.apple.com/source/xnu/xnu-1699.26.8/osfmk/i386/cpu_capabilities.h

FreeBSDでは 8-STABLEのサポートを切れば CLOCK_MONOTONIC_FAST を使えるとあるが、Linuxでは CLOCK_MONOTONIC_COARSE を2.6.32以前のサポートを切ればを使えるということは書かれていなかった。

// func now() (sec int64, nsec int32)
TEXT time·now(SB), NOSPLIT, $32
    MOVL    $232, AX // clock_gettime
    MOVQ    $0, DI      // CLOCK_REALTIME
    LEAQ    8(SP), SI
    SYSCALL
    MOVQ    8(SP), AX   // sec
    MOVQ    16(SP), DX  // nsec

    // sec is in AX, nsec in DX
    MOVQ    AX, sec+0(FP)
    MOVL    DX, nsec+8(FP)
    RET

TEXT runtime·nanotime(SB), NOSPLIT, $32
    MOVL    $232, AX
    // We can use CLOCK_MONOTONIC_FAST here when we drop
    // support for FreeBSD 8-STABLE.
    MOVQ    $4, DI      // CLOCK_MONOTONIC
    LEAQ    8(SP), SI
    SYSCALL
    MOVQ    8(SP), AX   // sec
    MOVQ    16(SP), DX  // nsec

    // sec is in AX, nsec in DX
    // return nsec in AX
    IMULQ   $1000000000, AX
    ADDQ    DX, AX
    MOVQ    AX, ret+0(FP)
    RET

Rust

  • 1.16.0-nightly

標準ライブラリstd::timeにおいて多くのプラットフォームでは Instant::nowCLOCK_MONOTONIC 固定、 SystemTime::nowCLOCK_REALTIME 固定となっている。macOSに関してはそれぞれ mach_absolute_time / gettimeofday を使用している。Rubyのソースのコメントからみるに CLOCK_MONOTONIC はLinux 2.5.63, FreeBSD 3.0, NetBSD 2.0, OpenBSD 3.4 (そしていつからかは不明だがここ最近のDragonflyBSD) で問題なく動くはずである。システムコール呼び出しに失敗した場合panicする。

Instant::now

  • mach_absolute_time (macOS/iOS)
  • CLOCK_MONOTONIC (Linux/Android)

SystemTime::now

  • gettimeofday (macOS/iOS)
  • CLOCK_REALTIME (Linux/Android)

rust-coarsetime crate

精度より速度が欲しい場合はrust-coarsetime が使える。rust-coarsetimeはLinux/OSX/FreeBSD(DragonflyBSD)においてそれぞれ CLOCK_MONOTONIC_COARSE / CLOCK_MONOTONIC_RAW_APPROX / CLOCK_MONOTONIC_FAST を使っている。

D言語

  • phobos (dmd-2.073.0)

時刻取得のAPIとして標準ライブラリPhobosの std.datetime に Clock.currTime / Clock.currStdTime があり、ClockTypeを指定することができる。いずれも内部的には currStdTime関数を呼んでいる。システムコール呼び出しに失敗した場合TimeException例外を送出する。

ClockTypeの指定によるcurrStdTime各OSごとの実装は以下の表にまとめる。Sierraの新APIサポートがなかったりここの分岐にないOS(例えばOpenBSD)はコンパイル時にUnsupported OSとして死ぬ。

ここで time(2)/gettimeofday(2) はそれぞれシステムコールを表し、定数っぽいやつは clock_gettime(2) の引数に渡される定数である。

  • ClockType.second
    • time(2) (Linux)
    • time(2) (OSX)
    • CLOCK_SECOND (FreeBSD 8.1)
    • time(2) (NetBSD)
    • time(2) (Solaris)
  • ClockType.coarse
    • CLOCK_REALTIME_COARSE (Linux 2.6.32)
    • gettimeofday(2) (OSX)
    • CLOCK_REALTIME_FAST (FreeBSD 8.1)
    • gettimeofday(2) (NetBSD)
    • CLOCK_REALTIME (Solaris)
  • ClockType.normal
    • CLOCK_REALTIME (Linux 2.5.63)
    • gettimeofday(2) (OSX)
    • CLOCK_REALTIME (FreeBSD 3.0)
    • gettimeofday(2) (NetBSD)
    • CLOCK_REALTIME (Solaris)
  • ClockType.precise
    • CLOCK_REALTIME (Linux 2.5.63)
    • gettimeofday(2) (OSX)
    • CLOCK_REALTIME_PRECISE (FreeBSD 8.1)
    • gettimeofday(2) (NetBSD)
    • CLOCK_REALTIME (Solaris)

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14