C言語: 実行時間測定の方法

Kazuki Ohta, 2005/12/30



C言語において実行時間を測定する為の方法はいくつかある。gettimeofday, clock, getrusage, timesを利用する方法である。ここではこれらの方法について検証してみる。これは2005/12/30時点での情報であり、古い亊が考えられるので注意して頂きたい。さらに、内容のほとんどはmanを移しただけなので、正確な情報を得るためにそれぞれの関数のmanを見ることを強く推奨する。

System: Linux 2.6.12
glibc: glibc 2.3.5-1ubuntu12

gettimeofdayを使用する方法

通常はこの関数を使用するのをお勧めする。
gettimeofdayはSVr4, BSD 4.3準拠である。返り値の型はsys/time.hに定義されるstruct timevalで有る。
struct timeval {
  time_t         tv_sec;  /* 秒 */
  suseconds_t    tv_usec; /* マイクロ秒 */
}
以下のようにして使用する。
#include <time.h>
#include <sys/time.h>
#include <stdio.h>

double gettimeofday_sec()
{
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return tv.tv_sec + (double)tv.tv_usec*1e-6;
}

int main()
{
    int i;
    int n = 0;
    double t1,t2;

    t1 = gettimeofday_sec();
    for (i = 0; i < 100000; i++)
        n *= i;
    t2 = gettimeofday_sec();
    printf("time = %10.30f\n", t2 - t1);

    t1 = gettimeofday_sec();
    for (i = 0; i < 10000000; i++)
        n *= i;
    t2 = gettimeofday_sec();
    printf("time = %10.30f\n", t2 - t1);
}
実行結果。
ubuntu% ruby -e '5.times { puts `./a.out` }'
time = 0.001516103744506835937500000000
time = 0.209121942520141601562500000000
time = 0.002206087112426757812500000000
time = 0.284270048141479492187500000000
time = 0.001529216766357421875000000000
time = 0.216418027877807617187500000000
time = 0.001517057418823242187500000000
time = 0.211354017257690429687500000000
time = 0.001515865325927734375000000000
time = 0.209794998168945312500000000000
単位は秒で有るので、clockと比べて相当な精度が出ている。ただし、gettimeofdayは単に経過時間を測定しているだけなので、バックグラウンドジョブが走っている場合は注意が必要で有る。

clockを使用する方法

clockはANSI Cで定義されている。返り値の型はclock_tとなっており、その精度はCPU時間で有る。秒単位での値を得る為には、CLOCKS_PER_SECで割る必要が有る。POSIXではCLOCKS_PER_SECが1000000である亊を要求している。
#include <time.h>
#include <stdio.h>

int main(void)
{
    int i;
    int n = 0;
    clock_t t1;
    clock_t t2;

    printf("CLOCKS_PER_SEC = %f\n", (double)CLOCKS_PER_SEC);

    t1 = clock();
    for (i = 0; i < 100000; i++)
        n *= i;
    t2 = clock();
    printf("time = %10.30f\n", (double)t2 - t1);

    t1 = clock();
    for (i = 0; i < 10000000; i++)
        n *= i;
    t2 = clock();
    printf("time = %10.30f\n", (double)t2 - t1);
}
実行結果。
ubuntu% ruby -e '5.times { puts `./a.out` }'
CLOCKS_PER_SEC = 1000000.000000
time = 0.000000000000000000000000000000
time = 110000.000000000000000000000000000000
CLOCKS_PER_SEC = 1000000.000000
time = 0.000000000000000000000000000000
time = 110000.000000000000000000000000000000
CLOCKS_PER_SEC = 1000000.000000
time = 0.000000000000000000000000000000
time = 120000.000000000000000000000000000000
CLOCKS_PER_SEC = 1000000.000000
time = 0.000000000000000000000000000000
time = 120000.000000000000000000000000000000
CLOCKS_PER_SEC = 1000000.000000
time = 0.000000000000000000000000000000
time = 120000.000000000000000000000000000000
これを見ると約1/100秒辺りまでの精度が出ているようである。

getrusageを使用する方法

getrusageはSVr4, BSD 4.3に準拠する。本来は資源の使用量を返す関数で有る。RUSAGE_CHILDRENを指定すれば子プロセスの使用する時間も計測出来る。
#include <time.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <stdio.h>

double getrusage_sec()
{
    struct rusage t;
    struct timeval tv;
    getrusage(RUSAGE_SELF, &t);
    tv = t.ru_utime;
    return tv.tv_sec + (double)tv.tv_usec*1e-6;
}

int main(void)
{
    int i;
    int n = 0;
    double t1,t2;

    t1 = getrusage_sec();
    for (i = 0; i < 100000; i++)
        n *= i;
    t2 = getrusage_sec();
    printf("time = %10.70f\n", t2 - t1);

    t1 = getrusage_sec();
    for (i = 0; i < 10000000; i++)
        n *= i;
    t2 = getrusage_sec();
    printf("time = %10.70f\n", t2 - t1);
}
実行結果。
ubuntu% ruby -e '5.times { puts `./a.out` }'
time = 0.0010000000000000000208166817117216851329430937767028808593750000000000
time = 0.1169819999999999887707602397313166875392198562622070312500000000000000
time = 0.0010000000000000000208166817117216851329430937767028808593750000000000
time = 0.1169819999999999887707602397313166875392198562622070312500000000000000
time = 0.0010000000000000000208166817117216851329430937767028808593750000000000
time = 0.1169819999999999887707602397313166875392198562622070312500000000000000
time = 0.0009989999999999998880895191177842207252979278564453125000000000000000
time = 0.1199819999999999914352954988316923845559358596801757812500000000000000
time = 0.0019989999999999999089062008295059058582410216331481933593750000000000
time = 0.1189819999999999905471170791315671522170305252075195312500000000000000
単位はstruct timevalを使用しているので、秒である。同じstruct timevalを使用するgettimeofdayよりも断然精度が出ている。バックグラウンドジョブにも左右されないので、通常はこれを使うのが良いと思われる。

timesを使用する方法

timesはSVr4, SVID, POSIX, X/OPEN, BSD 4.3に準拠している。返り値の型は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 */
};
使用方法は以下のようになる。
#include <time.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/times.h>
#include <stdio.h>

clock_t times_clock()
{
    struct tms t;
    return times(&t);
}

int main(void)
{
    int i;
    int n = 0;
    clock_t t1;
    clock_t t2;

    printf("sysconf(_SC_CLK_TCK) = %f\n", (double)sysconf(_SC_CLK_TCK));

    t1 = times_clock();
    for (i = 0; i < 100000; i++)
        n *= i;
    t2 = times_clock();
    printf("time = %10.30f\n", (double)t2 - t1);

    t1 = times_clock();
    for (i = 0; i < 10000000; i++)
        n *= i;
    t2 = times_clock();
    printf("time = %10.30f\n", (double)t2 - t1);
}
実行結果。
ubuntu% ruby -e '5.times { puts `./a.out` }'
sysconf(_SC_CLK_TCK) = 100.000000
time = 0.000000000000000000000000000000
time = 15.000000000000000000000000000000
sysconf(_SC_CLK_TCK) = 100.000000
time = 0.000000000000000000000000000000
time = 15.000000000000000000000000000000
sysconf(_SC_CLK_TCK) = 100.000000
time = 0.000000000000000000000000000000
time = 15.000000000000000000000000000000
sysconf(_SC_CLK_TCK) = 100.000000
time = 0.000000000000000000000000000000
time = 15.000000000000000000000000000000
sysconf(_SC_CLK_TCK) = 100.000000
time = 0.000000000000000000000000000000
time = 15.000000000000000000000000000000
clockとは違い、単位がsysconf(_SC_CLK_TCK)で定義されている事に注意したい。ここでは値が100になっているので、1/100秒の精度で測定出来る事が分かる。精度はclockと変わらないので、これを使うならclockを使う方が楽かな。

まとめ

CPU時間を測る場合、短い時間測定にはgetrusageを使うのが良いと思われる。長いのにはclockを使うのが良いと思われる。

ただし実際にはI/O等も含めたプログラム全体の測定するために、gettimeofdayを使う。

[ return ]