時間情報の取得方法と扱い方

作成:

ここではC言語での時間情報の取得方法について説明していく、 時間情報には様々な種類があり、また環境により利用できる関数も変わってくる。 C言語の環境であればどこでも利用できる標準関数から始まり、POSIX環境、Windows環境で利用できるAPIも紹介する。

POSIX環境

引き続き、Linux をはじめとする POSIX 環境で使用できる方法を紹介する。

clock_gettime()

前回説明したように、 POSIX環境で古くから使われてきたgettimeofday()は非推奨となり、この関数の利用が推奨されている。

プロトタイプ宣言は以下、C標準の関数ではないがヘッダは time.h である。

#include <time.h>
struct timespec {
  time_t tv_sec; /* Seconds.  */
  long tv_nsec;  /* Nanoseconds.  */
};
int clock_getres(clockid_t clk_id, struct timespec *res);
int clock_gettime(clockid_t clk_id, struct timespec *tp);

こちらの関数で取得できる値の構造体はstruct timespecである。 timespec_get()で紹介した構造体だ。 秒単位の値とナノ秒単位の値で時間を表現する。

clock_getresclk_idで示したクロックの精度をresに格納する。 これまで紹介してきた関数は格納先の構造体から最大の精度は分かっても、実際の精度は不明だったが、 この関数によって取得することが可能になっている。

clock_gettimeclk_idで示したクロックの時間情報をtpに格納する。 こちらが時間情報の関数となるが、clk_idで様々なタイプの時間情報を取得することができる。

クロックの種別についての詳細は MAN 等を参照してもらうとして、抜粋しておく

CLOCK_REALTIME
システムで一意な実時間、システム時間の手動設定やNTPによる変更の影響を受ける
CLOCK_REALTIME_COARSE
(Linux特有)高速だが精度の低い CLOCK_REALTIME
CLOCK_MONOTONIC
ある時点からの単調増加する時間、システム時間の手動設定の影響を受けない、 NTP などの段階的な調整の影響は受ける
CLOCK_MONOTONIC_COARSE
(Linux特有)高速だが精度の低い CLOCK_MONOTONIC
CLOCK_MONOTONIC_RAW
(Linux特有)ある時点からの単調増加する時間、システム時間の手動設定の影響も NTPなどの段階的な調整の影響も受けない
CLOCK_BOOTTIME
(Linux特有)CLOCK_MONOTONICと同じだが、システムがサスペンドされている間の時間も含む
CLOCK_PROCESS_CPUTIME_ID
そのプロセスが消費する CPU 時間
CLOCK_THREAD_CPUTIME_ID
そのスレッドが消費する CPU 時間

実時刻を取得したいならCLOCK_REALTIMEを使えば良い、 時刻ではなく、経過実時間が必要な場合はCLOCK_MONOTONICを使用する、 消費した CPU 時間が必要な場合はCLOCK_PROCESS_CPUTIME_IDする、 といったように使い分けることができる。 CLOCK_REALTIMEは実時刻なので取得した値を直接利用できる。 それ以外は、ある時点からの経過時間であり、 そのある時点は明確になっていないため、取得した値そのものにはあまり意味が無い。 2点以上で計測し、その差を利用する。

実験のため以下の様なコードを用意する。

#include <stdio.h>
#include <time.h>

int main(int argc, char **argv) {
  struct timespec ts;
  printf("time():  %10ld\n", time(NULL));

  clock_getres(CLOCK_REALTIME, &ts);
  printf("res :    %10ld.%09ld CLOCK_REALTIME\n", ts.tv_sec, ts.tv_nsec);
  clock_gettime(CLOCK_REALTIME, &ts);
  printf("time:    %10ld.%09ld CLOCK_REALTIME\n", ts.tv_sec, ts.tv_nsec);

#ifdef CLOCK_REALTIME_COARSE
  clock_getres(CLOCK_REALTIME_COARSE, &ts);
  printf("res :    %10ld.%09ld CLOCK_REALTIME_COARSE\n", ts.tv_sec, ts.tv_nsec);
  clock_gettime(CLOCK_REALTIME_COARSE, &ts);
  printf("time:    %10ld.%09ld CLOCK_REALTIME_COARSE\n", ts.tv_sec, ts.tv_nsec);
#endif
  clock_getres(CLOCK_MONOTONIC, &ts);
  printf("res :    %10ld.%09ld CLOCK_MONOTONIC\n", ts.tv_sec, ts.tv_nsec);
  clock_gettime(CLOCK_MONOTONIC, &ts);
  printf("time:    %10ld.%09ld CLOCK_MONOTONIC\n", ts.tv_sec, ts.tv_nsec);
#ifdef CLOCK_MONOTONIC_COARSE
  clock_getres(CLOCK_MONOTONIC_COARSE, &ts);
  printf("res :    %10ld.%09ld CLOCK_MONOTONIC_COARSE\n", ts.tv_sec, ts.tv_nsec);
  clock_gettime(CLOCK_MONOTONIC_COARSE, &ts);
  printf("time:    %10ld.%09ld CLOCK_MONOTONIC_COARSE\n", ts.tv_sec, ts.tv_nsec);
#endif
#ifdef CLOCK_MONOTONIC_RAW
  clock_getres(CLOCK_MONOTONIC_RAW, &ts);
  printf("res :    %10ld.%09ld CLOCK_MONOTONIC_RAW\n", ts.tv_sec, ts.tv_nsec);
  clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
  printf("time:    %10ld.%09ld CLOCK_MONOTONIC_RAW\n", ts.tv_sec, ts.tv_nsec);
#endif
#ifdef CLOCK_BOOTTIME
  clock_getres(CLOCK_BOOTTIME, &ts);
  printf("res :    %10ld.%09ld CLOCK_BOOTTIME\n", ts.tv_sec, ts.tv_nsec);
  clock_gettime(CLOCK_BOOTTIME, &ts);
  printf("time:    %10ld.%09ld CLOCK_BOOTTIME\n", ts.tv_sec, ts.tv_nsec);
#endif
  clock_getres(CLOCK_PROCESS_CPUTIME_ID, &ts);
  printf("res :    %10ld.%09ld CLOCK_PROCESS_CPUTIME_ID\n", ts.tv_sec, ts.tv_nsec);
  clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &ts);
  printf("time:    %10ld.%09ld CLOCK_PROCESS_CPUTIME_ID\n", ts.tv_sec, ts.tv_nsec);

  clock_getres(CLOCK_THREAD_CPUTIME_ID, &ts);
  printf("res :    %10ld.%09ld CLOCK_THREAD_CPUTIME_ID\n", ts.tv_sec, ts.tv_nsec);
  clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts);
  printf("time:    %10ld.%09ld CLOCK_THREAD_CPUTIME_ID\n", ts.tv_sec, ts.tv_nsec);

  printf("clock(): %20.9f\n", (double)(clock()) / CLOCKS_PER_SEC);
  return 0;
}

まずは、Ubuntu上で実行してみると

time():  1446729673
res :             0.000000001 CLOCK_REALTIME
time:    1446729673.346775809 CLOCK_REALTIME
res :             0.004000000 CLOCK_REALTIME_COARSE
time:    1446729673.345175591 CLOCK_REALTIME_COARSE
res :             0.000000001 CLOCK_MONOTONIC
time:          9617.304906812 CLOCK_MONOTONIC
res :             0.004000000 CLOCK_MONOTONIC_COARSE
time:          9617.303266756 CLOCK_MONOTONIC_COARSE
res :             0.000000001 CLOCK_MONOTONIC_RAW
time:          9616.862965705 CLOCK_MONOTONIC_RAW
res :             0.000000001 CLOCK_BOOTTIME
time:          9617.304961779 CLOCK_BOOTTIME
res :             0.000000001 CLOCK_PROCESS_CPUTIME_ID
time:             0.001448595 CLOCK_PROCESS_CPUTIME_ID
res :             0.000000001 CLOCK_THREAD_CPUTIME_ID
time:             0.001459506 CLOCK_THREAD_CPUTIME_ID
clock():          0.001466000

次に、Cygwin上で実行してみると

time():  1446729681
res :             0.015600100 CLOCK_REALTIME
time:    1446729681.142030400 CLOCK_REALTIME
res :             0.000000383 CLOCK_MONOTONIC
time:         13588.314030763 CLOCK_MONOTONIC
res :             0.015600100 CLOCK_PROCESS_CPUTIME_ID
time:             0.046800300 CLOCK_PROCESS_CPUTIME_ID
res :             0.015600100 CLOCK_THREAD_CPUTIME_ID
time:             0.046800300 CLOCK_THREAD_CPUTIME_ID
clock():          0.046000000

Linux 特有の値は Cygwin では取得できない。 時刻情報の精度という意味ではもちろんハードウェアに依存するが、 更にその上のOSがどのように扱うかによって変わってくるため、 取得できる精度に大きな違いがある。

また、上記例では参考までにtime()clock()の値を表示している。 CLOCK_REALTIMEの時のtv_secは、 time()と同じ値になっている。 一方、CLOCK_PROCESS_CPUTIME_IDで取得できる値は、 clock()で取得できる値と同じになっていると分かるだろう。