ファイヤープロジェクト
マイクロ秒の取得とナノ秒でのスリープ
2007-05-13T13:50+09:00   matsu
time関数やsleep関数は秒単位でしか処理できないが,ミリ秒とかマイクロ秒とか,より高精度な処理をしたいときのための関数,gettimeofdayとnanosleepを試してみた
マイクロ秒の取得と演算,ナノ秒でのスリープを行うサンプルを作成してみた.
実行結果は以下.
$ ./high-accuracy-time 
timerisset tv1 = 0 / tv2 = 0
timerisset tv1 = 1 / tv2 = 1
tv1 sec = 1179031840 / usec = 849800 => 1179031840.849800
tv2 sec = 1179031840 / usec = 951750 => 1179031840.951750
tv1 < tv2
tv2 - tv1 = 0.101950
tv1 + (tv2 - tv1) = 1179031840.951750
Sun May 13 13:50:40 2007
マイクロ秒を格納するには,long等は小さすぎるので,以下の構造体を使用する.
struct timeval
  {
    __time_t tv_sec;            /* Seconds.  */
    __suseconds_t tv_usec;      /* Microseconds.  */
  };
見たまんまだが,tv_secに秒を格納し,tv_usecにマイクロ秒を格納する. つまりマイクロ秒の精度で秒を表示すると
  // timervalの内容を表示
  fprintf(stdout, "tv1 sec = %ld / usec = %ld => %ld.%06ld\n",
	  tv1.tv_sec,
	  tv1.tv_usec,
	  tv1.tv_sec,
	  tv1.tv_usec
	  );
  fprintf(stdout, "tv2 sec = %ld / usec = %ld => %ld.%06ld\n",
	  tv2.tv_sec,
	  tv2.tv_usec,
	  tv2.tv_sec,
	  tv2.tv_usec);
となる. で,関数gettimeofdayは,このtimeval構造体に現在時刻を設定してくれる.
int gettimeofday(struct timeval *tv,
                 struct timezone *tz);
第二引数のtzにはタイムゾーン情報を設定してくれるらしいが,これからの時代を生きる人はNULLを設定すると思っておけばよいらしい.
  // timevalであるtv1を設定
  if (gettimeofday(&tv1, NULL))
    {
      perror("failed to gettimeofday");
      exit(1);
    }
返り値は0なら成功,-1なら失敗. 失敗した場合はerrnoが設定されるので,サンプルではperrorしてみた.
timevalは単純な型ではないので,初期化や比較,和差が単純な型ほど単純には記述できないが,マクロを使用すると単純に記述できる. まず初期化マクロ.
timerclear(tvp) \
  ((tvp)->tv_sec = (tvp)->tv_usec = 0)
tvpにはtimeval構造体のポインタを設定する.
  // timevalを初期化
  timerclear(&tv1);
  timerclear(&tv2);
そしてtimevalが値を設定された状態かどうかをチェックするマクロ.
timerisset(tvp) \
  ((tvp)->tv_sec || (tvp)->tv_usec)
tvpにはやはりチェック対象のtimevalのポインタを設定する.
  // timevalが設定されているかどうかを判定,表示
  fprintf(stdout, "timerisset tv1 = %d / tv2 = %d\n",
	  (timerisset(&tv1)),
	  (timerisset(&tv2)));
二つのtimevalを比較するマクロ.
timercmp(a, b, CMP) \
 (((a)->tv_sec == (b)->tv_sec) ? \
  ((a)->tv_usec CMP (b)->tv_usec) : \
  ((a)->tv_sec CMP (b)->tv_sec))
a,bは比較する二つのtimeval. CMPには演算子<,>,=のどれかを指定する. サンプルでは>,<を使用してtv1とtv2の大小関係を表示する文字の算出に使用した.
  // timercmpでtimevalを比較
  char c = '=' + (timercmp(&tv1, &tv2, >))
    - (timercmp(&tv1, &tv2, <));
  fprintf(stdout, "tv1 %c tv2\n", c);
timevalの引き算と足し算のマクロもある.
// 足し算マクロ
# define timeradd(a, b, result) \
  do { \
    (result)->tv_sec = (a)->tv_sec + (b)->tv_sec; \
    (result)->tv_usec = (a)->tv_usec + (b)->tv_usec; \
    if ((result)->tv_usec >= 1000000) \
      { \
        ++(result)->tv_sec; \
        (result)->tv_usec -= 1000000; \
      } \
  } while (0)

// 引き算マクロ
# define timersub(a, b, result) \
  do { \
    (result)->tv_sec = (a)->tv_sec - (b)->tv_sec; \
    (result)->tv_usec = (a)->tv_usec - (b)->tv_usec; \
    if ((result)->tv_usec < 0) { \
      --(result)->tv_sec; \
      (result)->tv_usec += 1000000; \
    } \
  } while (0)
どちらも第一引数と第二引数は演算オペランドのtimevalのポインタ,第三引数が結果を格納するtimevalへのポインタ.
  // timevalの差分算出
  timerclear(&tv3);
  timersub(&tv2, &tv1, &tv3);
  fprintf(stdout, "tv2 - tv1 = %ld.%06ld\n",
	  tv3.tv_sec,
	  tv3.tv_usec);

  // timevalの足し算
  timerclear(&tv4);
  timeradd(&tv1, &tv3, &tv4);
  fprintf(stdout, "tv1 + (tv2 - tv1) = %ld.%06ld\n",
	  tv4.tv_sec,
	  tv4.tv_usec);
ナノ秒を格納するには,long等は小さすぎるので,以下の構造体を使用する.
struct timespec
  {
    __time_t tv_sec;            /* Seconds.  */
    long int tv_nsec;           /* Nanoseconds.  */
  };
int
nanosleep(const struct timespec *req,
          struct timespec *rem);
第一引数にスリープする時間を格納したtimespecへのポインタを指定する. 返り値は0なら正常,それ意外はエラー. 返り値がEINTRならスリープ中に割り込みがあった場合であり,この場合はremにスリープしきれなかった秒数が設定される. サンプルでは,EINTRの場合は残りもしっかりスリープするようにループした.
  // 高精度時間データ
  req.tv_sec = 0;
  req.tv_nsec = 100000000;
  memcpy(&rem, &req, sizeof(req));

  int ret;
  do {
    memcpy(&req, &rem, sizeof(rem));
    memset(&rem, 0, sizeof(rem));
    // 高精度にスリープ
    ret = nanosleep(&req, &rem);
    if (ret != 0
	&& ret != EINTR)
      {
	perror("failed to nanosleep");
	exit(1);
      }
  } while (ret == EINTR);
ただしこんなにいろいろすると,わずかなスリープの場合,スリープ時間より準備時間などの方が長くなるので,サンプルでは0.1秒のスリープにしてみた.
matsu(C)
Since 2002
Mail to matsu