C++ chrono 라이브러리를 활용한 시간 간격 및 시점 관리

비율 표현을 위한 ratio

C++11에서 도입된 std::ratio는 컴파일 시점에 유리수(분수)를 표현하기 위한 템플릿 클래스입니다. 분자(N)와 분모(D)를 비타입 템플릿 매개변수로 받아 비율을 정의합니다.

template<std::intmax_t N, std::intmax_t D = 1>
class ratio;

표준 라이브러리는 자주 사용되는 SI 접두어를 기반으로 한 비율 타입을 미리 정의해 두고 있습니다.

  • std::kilo: 1000/1
  • std::centi: 1/100
  • std::milli: 1/1000
  • std::micro: 1/1000000
  • std::nano: 1/1000000000

시간 간격을 나타내는 duration

std::chrono::duration은 특정 시간 단위로 표현된 시간의 간격(길이)을 나타냅니다. 내부적으로 Rep(tick 수를 저장하는 데이터 타입)와 Period(tick의 길이를 정의하는 ratio)를 사용합니다.

template<class Rep, class Period = std::ratio<1>>
class duration;

다양한 시간 단위를 다음과 같이 선언할 수 있습니다.

std::chrono::duration<int, std::ratio<3600>> two_hours(2); // 2시간
std::chrono::duration<double, std::milli> half_ms(0.5);     // 0.5밀리초

표준 라이브러리는 편리함을 위해 일반적인 시간 간격 타입을 별칭으로 제공합니다:

  • std::chrono::hours, minutes, seconds
  • std::chrono::milliseconds, microseconds, nanoseconds

저장된 tick 수를 가져오려면 count() 멤버 함수를 호출합니다. 또한, 서로 다른 단위 간의 변환은 std::chrono::duration_cast를 통해 안전하게 수행할 수 있습니다.

std::chrono::seconds sec_interval(120);
// 초를 분으로 변환 (정밀도 손실이 없어야 하므로 duration_cast 사용)
auto min_interval = std::chrono::duration_cast<std::chrono::minutes>(sec_interval);
std::cout << min_interval.count() << " mins\n"; // 출력: 2 mins

특정 시점을 나타내는 time_point

time_point는 특정 시점(시간점)을 표현하며, 기준이 되는 클락(Clock)과 해당 클락의 에포크(Epoch)로부터 경과한 duration으로 구성됩니다.

template<class Clock, class Duration = typename Clock::duration>
class time_point;

현재 시스템 시간을 가져와 C 스타일의 시간 구조체로 변환하는 과정은 다음과 같습니다.

auto current_tp = std::chrono::system_clock::now();
std::time_t raw_time = std::chrono::system_clock::to_time_t(current_tp);

std::tm local_time_info;
localtime_r(&raw_time, &local_time_info); // 스레드 안전 버전 사용

char time_buffer[64];
std::strftime(time_buffer, sizeof(time_buffer), "%Y/%m/%d %H:%M:%S", &local_time_info);
std::cout << time_buffer << '\n';

time_point_cast를 사용하면 시간점의 정밀도(단위)를 조정할 수 있습니다.

시간을 측정하는 클락(Clock)의 종류

chrono 라이브러리는 용도에 따라 세 가지 주요 클락을 제공합니다.

1. system_clock

시스템의 실시간 시계(Wall-clock)를 나타냅니다. NTP 동기화 등으로 인해 시간이 뒤로 갈 수 있으므로(Monotonic하지 않음), 순수한 시간 간격 측정에는 부적합할 수 있습니다. 주로 현재 날짜와 시간을 표현하거나 time_t와 상호 변환할 때 사용합니다.

auto today = std::chrono::system_clock::now();
std::chrono::hours one_day(24);
auto tomorrow = today + one_day;

2. steady_clock

절대 조정되지 않으며 단조 증가(Monotonic)하는 클락입니다. tick 간의 간격이 일정하게 보장되므로, 알고리즘의 성능 측정이나 타임아웃 처리와 같은 시간 간격 측정에 가장 적합합니다.

3. high_resolution_clock

현재 시스템에서 사용 가능한 가장 높은 정밀도를 제공하는 클락입니다. 구현에 따라 steady_clock 또는 system_clock의 별칭일 수 있습니다.

코드 실행 시간 측정을 위한 RAII 타이머

기존의 매크로 기반 시간 측정 방식은 타입 안정성과 스코프 관리 측면에서 취약할 수 있습니다. 대신 현대 C++의 RAII(Resource Acquisition Is Initialization) 패턴을 활용하면 블록 단위의 실행 시간을 훨씬 깔끔하고 안전하게 측정할 수 있습니다. count()time_since_epoch()를 직접 호출하여 단위를 잘못 해석하는 실수를 방지하는 데도 도움이 됩니다.

#include <iostream>
#include <chrono>
#include <string>

class ScopedProfiler {
public:
    explicit ScopedProfiler(std::string task_name) 
        : name(std::move(task_name)), start_time(std::chrono::steady_clock::now()) {}

    ~ScopedProfiler() {
        auto end_time = std::chrono::steady_clock::now();
        auto elapsed_us = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time).count();
        
        if (elapsed_us >= 1000) {
            std::cout << "[Profile] " << name << " took " << elapsed_us / 1000.0 << " ms\n";
        } else {
            std::cout << "[Profile] " << name << " took " << elapsed_us << " us\n";
        }
    }

private:
    std::string name;
    std::chrono::steady_clock::time_point start_time;
};

int main() {
    {
        ScopedProfiler profiler("Heavy Computation");
        volatile long long sum = 0;
        for (int i = 0; i < 10000000; ++i) {
            sum += i;
        }
    } // 소멸자가 호출되며 자동으로 소요 시간 출력
    return 0;
}

태그: C++ chrono std::chrono Duration time_point

6월 30일 22:32에 게시됨