時間・時刻処理について(5)

このエントリーをはてなブックマークに追加

前回までは標準関数の範囲内での時間処理をする方法を解説してきました。今回からはシステム関数の解説に入ります。
今回は標準関数の範囲内では実現できなかった秒以下の時間の取得・設定について解説します。

時間取得・設定系関数

今回からはいよいよ、標準関数の範囲内では扱えなかった秒以下の時間を扱えるようになります。(clock(3)は別ですが...)
これにより時間について細かい情報の取得・制御が可能になってきます。
今回解説する関数は4つです。

関数名説明
times(2)現在のプロセッサ時間をstruct tms型で取得します(非推奨)
gettimeofday(2)struct timeval型で現在時間を取得します
getrusage(2)システムの資源の情報(時間を含めて)を取得します。


times(2)

clock(2)と似た動作で現在のプロセッサ時間を取得します。
clock(2)と異なるのは、struct tms型で取得出きるところにあります。struct tms型の宣言を見てみましょう。

struct tms  {
    clock_t tms_utime;  /* user time */
    clock_t tms_stime;  /* system time */
    clock_t tms_cutime; /* user time of children */
    clock_t tms_cstime; /* system time of children */
};

これを見ると分かりますが、取得できるプロセッサ時間の意味でそれぞれ分かれています。
childrenとあるのは子プロセスを指しています。"user time"、"system time"等も含めて先にこの部分の解説をします。

まずはこのコマンドの実行結果を見てください。簡単に解説するとtimeコマンドは指定されたコマンドの時間計測を行います。
locateは文字列(正規表現可能)でファイル名を検索します。

$ time locate *.c > /dev/null

real    0m4.546s
user    0m4.516s
sys     0m0.032s

locateコマンドの実行には、ほぼ4秒かかった事になります。
さて、timeコマンドの結果を見てください、real、user、sysと結果が3行に分かれて出力されています。
realは総実行時間です。コマンドが起動してから終了するまでの経過時間です。
userはコマンドがユーザ空間で動作した時間です。sysはカーネル空間で動作した時間です。
user/realの割合が多いほどOSへのCPUの切り替えが少ないので良い傾向ということになります。

このsysがstruct tmsのtms_stime、userがtms_utimeにあたります。childrenも同じ考えです。
では実際のソースコードを見ていきましょう。

//times.c
#include <stdio.h>
#include <unistd.h>
#include <sys/times.h>

int takeuti(unsigned long x, unsigned long y, unsigned long z)
{
	if (x <= y) {
		return z;
	}
	return takeuti(takeuti(x-1, y, z), takeuti(y-1, z, x), takeuti(z-1, x, y));
}

int main(void)
{
  struct tms t0;
  struct tms t1;

  times(&t0);

  takeuti(35,20,10);

  times(&t1);

  printf("_SC_CLK_TCK = %ld\n", sysconf (_SC_CLK_TCK));
  printf("%ld %ld\n", t1.tms_stime - t0.tms_stime, t1.tms_utime - t0.tms_utime);

  return 0;
}

今回は、時間を消費するためにWikiPedia竹内関数を使用しました。
竹内関数はたらいまわし関数ともよばれ、ベンチマークとしてよく利用されます。
今回はCということもありさほど大きくない値を入れています。実装が簡単でベンチマークには便利ですが、使用する際はオーバフローには気をつけてください。
times(2)自体の利用方法は非常に単純ですので説明は必要無いでしょう。

$ gcc -o times times.c
$ ./times
_SC_CLK_TCK = 100
0 452

実行してみると、システム空間の時間はまったく使わずに、ユーザ空間だけ使用している事が分かります。
これは竹内関数がユーザ空間のみで動作するからです。times(2)関数が正しく動作していることが分かります。
times(2)はclock(2)よりも便利ですが、注意事項もあります。manから引用します。

一秒あたりのクロック数は

sysconf(_SC_CLK_TCK);

を使って得ることが出来る。

clock(3) も clock_t 型の値を返すが、この値は times() で使用されるクロック tick 数ではなく、 CLOCKS_PER_SEC が単位である点に注意すること。

Linux では、times() の返り値を計算する起点となる「過去の任意の時点」は、カーネルのバージョンにより異なる。 Linux 2.4 以前では、この時点はシステムが起動である。Linux 2.6 以降では、この時点はシステム起動時刻の (2^32/HZ) - 300 (および4億2900万) 秒前である。このようにカーネルバージョン (や Unix の実装) により異なることと、返り値が clock_t の範囲をオーバーフローする可能性がある事実を考慮すると、移植性が必要なアプリケーションではこの値を使うのは避けるのが賢明であろう。
経過時間を測りたい場合には、代わりに gettimeofday(2) を使用すること。


つまり、まず実行時間は452/100=4.52秒という事。そして、汎用性を考慮するとtimes(2)は使用するなということです。
また、本関数を紹介したのは過去互換の為です。新規開発で使用してはいけません。
このためか、ネットを検索するとtimes(2)のサンプルプログラムがほとんどありませんでした。筆者も実用プログラムでは使った事がありません。

gettimeofday(2)

gettimeofday(2)はマイクロ秒(1/1000000秒)まで計測出来ます。Unixでは時間計測に非常に良く使われる関数です。
システム時間、ユーザ時間を気にしないのであればこの関数を使用しましょう。

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

int takeuti(unsigned long x, unsigned long y, unsigned long z)
{
	if (x <= y) {
		return z;
	}
	return takeuti(takeuti(x-1, y, z), takeuti(y-1, z, x), takeuti(z-1, x, y));
}

int main(void)
{
	struct timeval t0;
	struct timeval t1;

	gettimeofday(&t0, NULL);

	takeuti(35,20,10);

	gettimeofday(&t1, NULL);

	if (t1.tv_usec < t0.tv_usec) {
		printf("%d.%d\n", t1.tv_sec - t0.tv_sec - 1, 1000000 + t1.tv_usec - t0.tv_usec);
	}
	else {
		printf("%d.%d\n", t1.tv_sec - t0.tv_sec, t1.tv_usec - t0.tv_usec);
	}

	return 0;
}

ここでgettimeofday(2)について多少の解説が必要でしょう。
gettimeofday(2)のプロトタイプは次のように宣言されています。第1引数はstruct timeval型で現在の時間を格納します。
第2引数はタイムゾーンを取得しますが、通常は使う必要はないのでNULLにしています。
struct timeval型は<sys/time.h>で定義されていて、秒とマイクロ秒の2つのデータを持っています。

#include <sys/time.h>

int gettimeofday(struct timeval *tv, struct timezone *tz);

struct timeval {
	time_t      tv_sec;     /* 秒 */
	suseconds_t tv_usec;    /* マイクロ秒 */
};

struct timeval型は秒とマイクロ秒を別で持っているので、時間差を計算するのが少し面倒です。
timeコマンドを利用して動作させると正しく時間計測ができている事が分かります。

$ gcc -o gettimeofday gettimeofday.c
$ ./gettimeofday
4.440295

real    0m4.441s
user    0m4.420s
sys     0m0.000s

gettimeofday(2)はPOSIX、SVr4, 4.3BSD に準拠しているので移植性が高くマイクロ秒までの分解能があり非常に便利な関数です。
ぜひ使い方をマスターしましょう。

getrusage(2)

gettimeofday(2)は非常に便利な関数です。しかしシステム時間、ユーザ時間が取得できないという欠点があります。
これは通常の方法では取得できません。そこでgetrusage(2)を使用します。
そもそもgetrusage(2)は時間取得だけを目的にした関数ではありません。OSの資源使用量を取得する関数です。
取得できるデータはstruct rusage型のデータです。struct rusage型の宣言をmanから引用します。

struct rusage {
	struct timeval ru_utime; /* 使用されたユーザー時間 */
	struct timeval ru_stime; /* 使用されたシステム時間 */
	long   ru_maxrss;        /* RAM 上に存在する仮想ページのサイズ
	                            (resident set size) の最大値 */
	long   ru_ixrss;         /* 共有メモリの合計サイズ */
	long   ru_idrss;         /* 非共有データの合計サイズ */
	long   ru_isrss;         /* 非共有スタックの合計サイズ */
	long   ru_minflt;        /* 利用されたページ */
	long   ru_majflt;        /* ページフォールト */
	long   ru_nswap;         /* スワップ */
	long   ru_inblock;       /* ブロック入力操作 */
	long   ru_oublock;       /* ブロック出力操作 */
	long   ru_msgsnd;        /* 送信されたメッセージ */
	long   ru_msgrcv;        /* 受信されたメッセージ */
	long   ru_nsignals;      /* 受信されたシグナル */
	long   ru_nvcsw;         /* 意図したコンテキスト切り替え */
	long   ru_nivcsw;        /* 意図しないコンテキスト切り替え */
};

様々な値が取得出来ることが分かります。また取得対象も現在のプロセスか子プロセスやスレッドかの選択も加能です。
取得できる時間もstruct timeval型ですのでgettimeofday(2)で対応出来ない要求も満たせます。

ではソースコードに移ります。

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

int takeuti(unsigned long x, unsigned long y, unsigned long z)
{
	if (x <= y) {
		return z;
	}
	return takeuti(takeuti(x-1, y, z), takeuti(y-1, z, x), takeuti(z-1, x, y));
}

int main(void)
{
	struct rusage usage0;
	struct rusage usage1;

	getrusage(RUSAGE_SELF, &usage0);

	takeuti(35,20,10);

	getrusage(RUSAGE_SELF, &usage1);

	if (usage1.ru_utime.tv_usec < usage0.ru_utime.tv_usec) {
		printf("%d.%d\n", usage1.ru_utime.tv_sec - usage0.ru_utime.tv_sec - 1, 1000000 + usage1.ru_utime.tv_usec - usage0.ru_utime.tv_usec);
	}
	else {
		printf("%d.%d\n", usage1.ru_utime.tv_sec - usage0.ru_utime.tv_sec, usage1.ru_utime.tv_usec - usage0.ru_utime.tv_usec);
	}

	return 0;
}

第一引数は状況に合わせて指定します。今回は現在のプロセスの情報対象ですのでRUSAGE_SELFを使用します。
getrusage(2)の宣言と引数についてmanから引用しましょう。

#include <sys/time.h>
#include <sys/resource.h>

int getrusage(int who, struct rusage *usage);

getrusage() は who の資源 (resource) の使用量を返す。 who には以下のいずれか一つを指定できる。

RUSAGE_SELF
       呼び出したプロセスの資源使用量、 そのプロセス内の全スレッドが使用している資源の合計を返す。

       前者は呼び出したプロセスのリソース使用量を要求し、 後者は呼び出したプロセスの子供のうち 終了して待ち状態にあるプロセスの使用量を要求する。

RUSAGE_CHILDREN
       呼び出したプロセスの子プロセスのうち、 終了して待ち状態にある全プロセスが使用している資源使用量の統計を返す。 これらの統計情報には、孫プロセスやその子セスのうち、 削除待ちのものが使用している資源も含まれる。

RUSAGE_THREAD (Linux 2.6.26 以降)
       呼び出したスレッドに関する資源使用量の統計を返す。


第一引数は状況に合わせて指定します。今回は現在のプロセスの情報対象ですのでRUSAGE_SELFを使用します。

実行するとgettimeofday(3)と同様の結果になることが分かります。
使い方としては、普段はgettimeofday(2)で必要な時だけgetrusage(2)を使うという切り分けになるでしょう。
注意事項として、getrusage(2)は対象のプロセスなどが使用したリソース、今回は時間です、を取得する点です。実働時間ではありませんので注意してください。

$ gcc -o getrusage getrusage.c
$ ./getrusage
4.388274

real    0m4.391s
user    0m4.392s
sys     0m0.000s

まとめ

これで標準関数では出来なかった秒以下の時間取得が出来るようになりました。
次回はスリープ関係の関数です。今回解説した関数も使用しながら解説していきます。