8ttyan’s blog

コンピュータ関係の小ネタ

C言語で正確にミリ秒マイクロ秒単位まで計測する方法

C標準ライブラリには多数の時間に関するライブラリがある.
注目すべきはtime.hsys/time.hである.よくあるのはtime.hstruct time_tに1970年1月1日からの秒数を格納し,それを年月日時分秒をメンバ変数として持つ構造体struct tmに変換して得る方法である.
しかし,sys/time.hではさらに上位の(?)構造体timevalが定義されており,メンバ変数としてtime_t型のtv_secとマイクロ秒単位で時刻を格納するsuseconds_t型のtv_usecが定義されている:

/* <time.h>および<sys/time.h>から構造体の定義を抜粋 */

    // timevalは基準年からの秒数とマイクロ秒を格納
    struct timeval {
        time_t      tv_sec;     /* 秒 */
        suseconds_t tv_usec;    /* マイクロ秒 */
    };
    // time_t型は基準年からの秒数
    // time_tのままでは使いにくい.time_tはtm構造体に相互に変換できる
    struct tm {
        int tm_sec;        /* 秒 (0-60) */
        int tm_min;        /* 分 (0-59) */
        int tm_hour;       /* 時間 (0-23) */
        int tm_mday;       /* 月内の日付 (1-31) */
        int tm_mon;        /* 月 (0-11) */
        int tm_year;       /* 年 - 1900 */
        int tm_wday;       /* 曜日 (0-6, 日曜 = 0) */
        int tm_yday;       /* 年内通算日 (0-365, 1 月 1 日 = 0) */
        int tm_isdst;      /* 夏時間 */
    };

これらを用いて現在日時時刻を取得&表示する例

#include <stdio.h>
#include <time.h>
#include <sys/time.h>
// time.hとsys/time.hは別物なので注意!

int main() {

    /* まず,日時・時刻情報を格納するための変数を宣言 */
    struct timeval myTime;    // time_t構造体を定義.1970年1月1日からの秒数を格納するもの
    struct tm *time_st;       // tm構造体を定義.年月日時分秒をメンバ変数に持つ構造体
    const char weekName[7][4] = {   // 曜日は数字としてしか得られないので,文字列として用意
        "Sun",
        "Mon",
        "Tue",
        "Wed",
        "Thu",
        "Fri",
        "Sat"
    };

    /* 時刻取得 */
    gettimeofday(&myTime, NULL);    // 現在時刻を取得してmyTimeに格納.通常のtime_t構造体とsuseconds_tに値が代入される
    time_st = localtime(&myTime.tv_sec);    // time_t構造体を現地時間でのtm構造体に変換

    printf("Date : %d/%02d/%02d(%s) %02d:%02d:%02d.%06d\n",     // 現在時刻
                time_st->tm_year+1900,     // 年
                time_st->tm_mon+1,         // 月
                time_st->tm_mday,          // 日
                weekName[time_st->tm_wday],// 曜日
                time_st->tm_hour,          // 時
                time_st->tm_min,           // 分
                time_st->tm_sec,           // 秒
                myTime.tv_usec            // マイクロ秒
                );

}

これを実行すると

Date : 2015/02/03(Tue) 00:27:10.386185

といった感じで表示される. ちなみに,マイクロ秒単位まで時刻を取得する方法として,

#include <time.h>
(略)
clock_t microSec;
microSec = clock();
printf("%d\n", microSec/CLOCKS_PER_SEC);

などとする方法が見受けられるが,これは間違いである.これはCPU時間なのでCPU稼働率が100%なら正しい時刻を示すが,そうでない場合は時計の時間とは異なる.特にopenmpなどを用いて並列化すると(CPU稼働率が数百パーセントなどとなるので)意味がない.
逆にこれを利用して、簡易的に該当プログラムのCPU占有率を調べることもできる:

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

int main() {
    
    struct timeval startTime, endTime;  // 構造体宣言
    clock_t startClock, endClock;       // clock_t型変数宣言
    
    gettimeofday(&startTime, NULL);     // 開始時刻取得
    startClock = clock();               // 開始時刻のcpu時間取得
    
//    (何らかの処理)
    
    gettimeofday(&endTime, NULL);       // 開始時刻取得
    endClock = clock();                 // 開始時刻のcpu時間取得
    
    time_t diffsec = difftime(endTime.tv_sec, startTime.tv_sec);    // 秒数の差分を計算
    suseconds_t diffsub = endTime.tv_usec - startTime.tv_usec;      // マイクロ秒部分の差分を計算
    //以下の処理は不要(15/10/28)
    //if (diffsub < 0) {                                              // マイクロ秒が負になったとき
    //    diffsec -= 1;                                               // 秒部分を繰り下げ
    //    diffsub = 1000000 + diffsub;                                // 1秒との差
    //}
    double realsec = diffsec+diffsub*1e-6;                          // 実時間を計算
    double cpusec = (endClock - startClock)/(double)CLOCKS_PER_SEC; // cpu時間を計算
    
    double percent = 100.0*cpusec/realsec;                          // 使用率を100分率で計算
    printf("CPU使用率%f %%\n", percent);                            // 表示
    
    return 0;
           
}