ubichupas.net

C言語の時刻取得はgettimeofday()でなくclock_gettime()を使う

C言語で現在時刻を取得する関数gettimeofday()が廃止予定となったのが POSIX.1-2008の話らしいので、もう10年も経っています。 しかしコンパイル時に警告が出るわけでもないため 古いコードの流用時につい直し忘れてしまうのです。 そこで戒めとして代替関数であるclock_gettime()の使い方についてメモを残しておきます。

clock_gettime()gettimeofday()
#include <time.h>

struct timespec {
    time_t   tv_sec;
    long     tv_nsec;
};
#include <sys/time.h>

struct timeval {
    time_t      tv_sec;
    suseconds_t tv_usec;
};

clock_gettime()で現在時刻を表示

システム時刻を取得する場合はCLOCK_REALTIMEを指定します。 取得した.tv_secを文字列変換するのはこれまで通りlocaltime()とstrftime()です。 システム時刻なのでコマンドやNTPによって時間が遡ることもあります。
そういえばCentOS 7にはtimedatectlという時刻操作を統括するコマンドが増えていました。

time_test.c
#include <stdio.h>
#include <time.h>

void date_rfc3339(void)
{
    char  date[48];

    struct timespec  now;
    clock_gettime(CLOCK_REALTIME, &now);
    struct tm  local;
    localtime_r(&now.tv_sec, &local);

    size_t  len = strftime(date, sizeof(date), "%FT%T", &local);
    len += (size_t)snprintf(&date[len], sizeof(date) - len, ".%06ld", now.tv_nsec / 1000);
    len += strftime(&date[len], sizeof(date) - len, "%z", &local);
    date[len + 1] = '\0';
    date[len - 0] = date[len - 1];
    date[len - 1] = date[len - 2];
    date[len - 2] = ':';

    puts(date);
}

pthread_cond_timedwait()で待ち合わせを行う

引数にstruct timespecをとる関数の1つに pthread_cond_timedwait()があります。 これを使う際の注意点として、条件変数の デフォルト属性がCLOCK_REALTIMEなので CLOCK_MONOTONICに変更した方がよいです。 本当はCLOCK_MONOTONIC_RAWを指定したいのですけど、 pthread_condattr_setclock()にEINVALを返されました。残念。

pthread_condattr_t  attr;
pthread_condattr_init(&attr);
pthread_condattr_setclock(&attr, CLOCK_MONOTONIC);
pthread_cond_init(&cond, &attr);
pthread_condattr_destroy(&attr);

単純なスリープについても引数にstruct timespecをとる関数 nanosleep()またはclock_nanosleep()を今後は使いましょう。

clock_gettime()で処理時間を測定

clock_gettime()では実時間の他に CLOCK_THREAD_CPUTIME_IDを指定して スレッドがCPUを使用した時間を取得することもできます。 測定を別のスレッドで行う場合はpthread_getcpuclockid()で スレッド識別子からクロック識別子を取得してそれを指定します。

time_test.c
#include <stdio.h>
#include <time.h>
#include <pthread.h>
#include <errno.h>

pthread_cond_t  cond;
pthread_mutex_t  mutex;

int  primes[500];
int  primes_num;

void *primes_generator(void *dmy)
{
    int   n, i;

    struct timespec  timeout = { .tv_nsec = 100000 };
    nanosleep(&timeout, NULL);

    primes[primes_num ++] = 2;
    for( n = 3; primes_num < sizeof(primes) / sizeof(primes[0]); n += 2 )
    {
        for( i = 0; i < primes_num; i ++ )
            if( 0 == n % primes[i] )  break;

        if( i == primes_num )
        {
            primes[primes_num ++] = n;

            if( 0 == primes_num % 100 )
                pthread_cond_broadcast(&cond);
        }
    }

    nanosleep(&timeout, NULL);

    return NULL;
}


static inline void clocksub(struct timespec *a, struct timespec *b, struct timespec *result)
{
    result->tv_sec  = b->tv_sec  - a->tv_sec;
    result->tv_nsec = b->tv_nsec - a->tv_nsec;
    if( result->tv_nsec < 0 )
    {
        result->tv_sec  -= 1;
        result->tv_nsec += 1000000000;
    }
}


int main(int argc, char *argv[])
{
    int  ret;

    pthread_mutex_init(&mutex, NULL);

    pthread_condattr_t  attr;
    pthread_condattr_init(&attr);
    pthread_condattr_setclock(&attr, CLOCK_MONOTONIC);
    pthread_cond_init(&cond, &attr);
    pthread_condattr_destroy(&attr);

    pthread_t  primes_id;
    pthread_create(&primes_id, NULL, primes_generator, NULL);

    clockid_t  primes_clock;
    pthread_getcpuclockid(primes_id, &primes_clock);

    struct timespec  main_ts[3], primes_ts[3];
    clock_gettime(CLOCK_THREAD_CPUTIME_ID, &main_ts[0]);
    clock_gettime(primes_clock, &primes_ts[0]);

    do
    {
        date_rfc3339();

        pthread_mutex_lock(&mutex);

        struct timespec  timeout;
        clock_gettime(CLOCK_MONOTONIC, &timeout);
        timeout.tv_nsec += 500000;
        ret = pthread_cond_timedwait(&cond, &mutex, &timeout);

        pthread_mutex_unlock(&mutex);

        clock_gettime(CLOCK_THREAD_CPUTIME_ID, &main_ts[1]);
        clock_gettime(primes_clock, &primes_ts[1]);

        clocksub(&main_ts[0], &main_ts[1], &main_ts[2]);
        clocksub(&primes_ts[0], &primes_ts[1], &primes_ts[2]);
        printf("%15s : %ld.%06ld\n", "main", main_ts[2].tv_sec, main_ts[2].tv_nsec / 1000);
        printf("%15s : %ld.%06ld\n", "primes", primes_ts[2].tv_sec, primes_ts[2].tv_nsec / 1000);
        main_ts[0] = main_ts[1];
        primes_ts[0] = primes_ts[1];
    } while( ETIMEDOUT != ret );

    date_rfc3339();

    pthread_join(primes_id, NULL);
    pthread_cond_destroy(&cond);
    pthread_mutex_destroy(&mutex);

    return 0;
}

例として素数を求めるスレッドを作ってメインスレッドから測定してみました。 メインスレッドの方は待っているだけなのでCPUを使用していないことがわかります。

localhost
[ubichupas@localhost]$ gcc -Wall -o time_test time_test.c -lpthread
[ubichupas@localhost]$ ./time_test
2018-05-06T15:59:18.705295+09:00
           main : 0.000419
         primes : 0.000348
2018-05-06T15:59:18.705801+09:00
           main : 0.000085
         primes : 0.000315
2018-05-06T15:59:18.706106+09:00
           main : 0.000049
         primes : 0.000441
2018-05-06T15:59:18.706548+09:00
           main : 0.000104
         primes : 0.000678
2018-05-06T15:59:18.707226+09:00
           main : 0.000062
         primes : 0.000641
2018-05-06T15:59:18.707869+09:00

最後にタイムゾーンに関する情報はグローバル変数で printf("TZ=%s%+ld\n", tzname[0], timezone / 60 / 60); な風に参照できるため、gettimeofday()はもう使用しません。

0 件のコメント: