マイクロ秒の取得とナノ秒でのスリープ
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秒のスリープにしてみた.

