mcommit's message

ソフトウェア開発の仕事をしているsimotinといいます。記事の内容でご質問やご意見がありましたらお気軽にコメントしてください\^o^/

Linux C言語でミリ秒の計算をする

Linux系OSのC言語ではtimeval構造体を使った時間表現があります。

しかしながら、あくまでgettimeofdayによる時間の取得・参照を目的としているからなのか、時間計算のAPIはありません。

ms秒単位で経過時間の加算をする関数が必要になったので書いてみたのですが、テストのために書いた検証コードを動かしてみたら、結果が面白かったので記事として挙げておきたいと思います。

ミリ秒加算するコード

#include <stdio.h>
#include <unistd.h>
#include <sys/time.h>

void add_ms(struct timeval *t, long ms) {
	long add_us = ms * 1000;
	long tmp_us = t->tv_usec + add_us;
	if (1000000 <= tmp_us) {
		t->tv_sec++;
		t->tv_usec = tmp_us - 1000000;
	}
	else {
		t->tv_usec += add_us;
	}
}

int main(int argc, char **argv) {
	struct timeval calc, now;
	gettimeofday(&now, NULL);

	calc = now;

	printf("------------------------------------------\n");
	printf("Start Test\n");
	printf("now  [%ld-ld]\n", now.tv_sec, now.tv_usec);
	printf("calc [%ld-ld]\n", calc.tv_sec, calc.tv_usec);
	printf("------------------------------------------\n");
	while(1) {
		usleep(1000 * 1);
		add_ms(&calc, 1);
		gettimeofday(&now, NULL);
		printf("------------------------------------------\n");
		printf("now  [%ld-ld]\n", now.tv_sec, now.tv_usec);
		printf("calc [%ld-ld]\n", calc.tv_sec, calc.tv_usec);
		printf("------------------------------------------\n");
	}
	return 0;
}

汎用OSの限界

加算処理が正しく動くか確認するため、現在時刻と書いてみた関数(add_ms)で計算した時刻の2つを縦に並べて表示しました。

usleep関数はマイクロ秒単位でのスリープ処理になります。

私の手元の環境では、上記の1ms単位での確認では徐々に計算した時刻の方が遅れていきました。
100msとか1secだとほぼ同じくらいに進んでいきますが、だいたい数十ミリ秒程度はcalcの方が遅れていました。

やはり、タイムシェアリングで動作する汎用OSだと、当然ですが精度的には限界があるようです。

usleepの精度

試しにusleepとgettimeofdayを繰り返すコードに修正して試してみましたが、

usleep(10);

としても大体100usの誤差が出ていました。

usleep関数はマイクロ秒オーダーのスリープ関数ですが、名前を信じて

usleep(1);

とかしてその通りに動くことは期待してはいけません

名前負けしてる関数なんです。

試した環境はCentOSとWindows10上のcygwinを使いましたが汎用OSで他のプロセスやカーネルの処理、ディスパッチにミリ秒かからないのであれば、むしろ頑張っているなぁという気もします。

感想

ということで実際に検証してみて、書いてみた関数の挙動的には問題はなさそうですが、こういった時間計算の関数が標準ライブラリに含まれない理由がなんとなく分かった気がしました。

日付や時間の分野は、扱うのが簡単見えてすごく難しいですね。

それにしてもusleepの実装が気になるところではあります。

後で見てみよう・・・