Linux clock_gettime API · POSIX C · 시계별 의미 구분

경과 시간과 CPU 시간을 따로 측정합니다

CLOCK_MONOTONIC과 CPU 시간 시계를 같은 구간에 사용해, 기다린 시간과 실제 계산 시간을 구분합니다.

먼저 무엇을 재려는지 정합니다

시계측정 대상
CLOCK_REALTIME달력 시각입니다. 관리자의 시각 변경 등으로 값이 뛰거나 되돌아갈 수 있습니다.
CLOCK_MONOTONIC경과 시간에 적합한 단조 시계입니다. Linux에서는 suspend 시간을 제외합니다.
CLOCK_BOOTTIMEMONOTONIC과 비슷하지만 suspend 시간도 포함합니다.
CLOCK_MONOTONIC_RAW주파수 보정을 적용하지 않는 시간입니다. suspend 시간은 제외합니다.
CLOCK_PROCESS_CPUTIME_ID프로세스의 모든 스레드가 사용한 CPU 시간입니다.
CLOCK_THREAD_CPUTIME_ID호출한 스레드가 사용한 CPU 시간입니다.

CLOCK_MONOTONIC은 clock_settime으로 임의 설정할 수 없습니다. 다만 NTP 등의 점진적인 주파수 보정은 영향을 줄 수 있습니다. 연속 호출의 값이 같을 수도 있으므로 “매 호출마다 반드시 증가한다”는 뜻으로 이해하면 안 됩니다.

clock()도 CPU 사용 시간을 나타내는 함수입니다. clock_gettime보다 무조건 덜 정확한 경과 시간 함수로 비교하면 측정 대상부터 달라집니다.

기다리는 동안 두 시계는 다르게 움직입니다

동일한 구간에 대한 두 가지 측정
  1. 실행 중인 구간경과 시간과 CPU 시간이 모두 증가합니다.
  2. 스레드가 잠든 구간경과 시간은 증가합니다. 그 스레드의 CPU 사용 시간은 거의 늘지 않습니다.
  3. 다시 실행된 구간시계 두 개를 다시 읽어 각각 차이를 구합니다.

상자의 순서는 프로그램 진행 순서입니다. CPU 시간은 실제 실행에 소비한 시간이며, 멀티스레드 프로세스에서는 여러 CPU에서 소비한 시간을 합산합니다.

250ms 대기 구간을 측정합니다

#define _POSIX_C_SOURCE 200809L
#include <errno.h>
#include <stdio.h>
#include <time.h>

static double delta(struct timespec a, struct timespec b)
{
    return (double)(b.tv_sec - a.tv_sec)
         + (double)(b.tv_nsec - a.tv_nsec) / 1000000000.0;
}

int main(void)
{
    struct timespec wall0, wall1, cpu0, cpu1;
    struct timespec delay = { .tv_sec = 0, .tv_nsec = 250000000 };
    if (clock_gettime(CLOCK_MONOTONIC, &wall0) == -1 ||
        clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &cpu0) == -1) {
        perror("clock_gettime");
        return 1;
    }
    while (nanosleep(&delay, &delay) == -1) {
        if (errno != EINTR) {
            perror("nanosleep");
            return 1;
        }
    }
    if (clock_gettime(CLOCK_PROCESS_CPUTIME_ID, &cpu1) == -1 ||
        clock_gettime(CLOCK_MONOTONIC, &wall1) == -1) {
        perror("clock_gettime");
        return 1;
    }
    printf("elapsed=%.6f s\ncpu=%.6f s\n",
           delta(wall0, wall1), delta(cpu0, cpu1));
    return 0;
}

Linux에서 cc -std=c17 -Wall -Wextra clocks.c -o clocks로 빌드한 뒤 실행합니다. glibc 2.17보다 오래된 환경에서는 -lrt가 필요할 수 있습니다.

코드 부분설명
wall0, cpu0같은 작업을 시작하기 전 서로 다른 시계의 기준값을 저장합니다.
nanosleep호출 스레드를 대기시킵니다. 신호로 중단되면 EINTR을 구분하고 남은 시간을 다시 기다립니다.
wall1, cpu1작업 후 같은 종류의 시계를 다시 읽습니다. 다른 종류의 시계끼리 빼면 안 됩니다.
delta초 차이와 나노초 차이를 합쳐 초 단위의 실수로 계산합니다. 나노초 부분만 음수가 될 수 있어 두 항을 함께 계산합니다.

정상적인 단일 스레드 실행에서는 경과 시간이 약 0.25초 이상이고 CPU 시간은 훨씬 짧게 나오는 것을 관찰할 수 있습니다. 정확한 수치는 시스템 부하와 실행 환경마다 다릅니다. 스케줄링 대기 때문에 경과 시간이 요청한 대기 시간보다 길 수 있습니다.

표시 단위와 측정 정밀도는 다릅니다

timespec이 나노초 필드를 제공한다는 사실만으로 측정 오차가 1ns라는 뜻은 아닙니다. clock_getres로 보고되는 해상도, 시계 읽기 비용, 측정 대상의 실행 변동을 구분해야 합니다. 짧은 동작은 반복 측정하고 측정 코드 자체의 비용도 함께 살펴보는 편이 좋습니다.

위 예제는 두 시계를 순서대로 읽으므로 시작과 종료 경계가 완전히 같은 순간은 아닙니다. 시계의 의미를 비교하는 예제이며, 하드웨어의 최소 실행 지연을 정밀 측정하는 벤치마크는 아닙니다.

확인한 문서

Linux clock_gettime API · POSIX C · 시계별 의미 구분