Chrono

Gaon12 (토론 / 기여)님의 2025년 7월 27일 (일) 18:59 판 (문서 개선(구성 요소 등 추가))

개요

std::chrono는 C++11부터 표준 라이브러리에 추가된 시간 관련 기능을 제공하는 라이브러리이다. 이 라이브러리는 고정밀의 시간 측정, 시간 간격 계산, 그리고 다양한 시간 단위 간의 변환을 지원한다. 기존의 C 스타일 시간 함수들과 달리 타입 안전성을 보장하며, 컴파일 타임에 단위 변환 오류를 감지할 수 있다.

chrono 라이브러리는 다음과 같은 주요 구성 요소로 이루어져 있다:

  • Clock: 시간을 측정하는 시계
  • Time Point: 특정 시점을 나타내는 객체
  • Duration: 시간 간격을 나타내는 객체

주요 구성 요소

Clock 타입

chrono 라이브러리는 다음과 같은 세 가지 주요 clock을 제공한다:

system_clock

시스템의 실시간 시계를 나타낸다. 이 시계는 시스템 관리자가 조정할 수 있으며, UTC 시간을 기준으로 한다. 일반적으로 Unix epoch(1970년 1월 1일 00:00:00 UTC)부터의 시간을 측정한다.

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

steady_clock

단조 증가하는 시계로, 시스템 시간 조정의 영향을 받지 않는다. 성능 측정에 가장 적합한 시계이다. 이 시계는 절대 뒤로 가지 않으며, 균등한 속도로 진행된다.

auto start = std::chrono::steady_clock::now();
// 측정할 코드
auto end = std::chrono::steady_clock::now();

high_resolution_clock

사용 가능한 가장 높은 해상도의 시계이다. 일반적으로 steady_clock의 별칭이다.

Duration

시간 간격을 나타내는 템플릿 클래스이다. 두 개의 템플릿 매개변수를 가진다:

  • Rep: 틱의 개수를 저장하는 산술 타입
  • Period: 각 틱이 나타내는 시간 간격 (std::ratio로 표현)

표준 라이브러리는 다음과 같은 미리 정의된 duration 타입들을 제공한다:

  • nanoseconds: 나노초 (10^-9 초)
  • microseconds: 마이크로초 (10^-6 초)
  • milliseconds: 밀리초 (10^-3 초)
  • seconds: 초
  • minutes: 분
  • hours: 시간

Time Point

특정 시점을 나타내는 템플릿 클래스이다. 특정 epoch(기준점)으로부터의 duration으로 표현된다.

틱과 정밀도

[1]은 시간 측정의 최소 단위이다. chrono 라이브러리에서는 나노초(ns), 마이크로초(μs), 밀리초(ms), 초(s), 분(m), 시간(h) 등 다양한 단위를 선택할 수 있다.

시간 측정의 정밀도는 사용하는 시계와 시스템에 따라 달라진다. 일반적으로 steady_clock이 가장 정확한 성능 측정을 제공한다.

기본 사용법

간단한 시간 측정

#include <iostream>
#include <chrono>
#include <thread>

int main() {
    using namespace std::chrono;
    
    // 시작 시간 기록
    auto start = steady_clock::now();
    
    // 측정할 코드 (예: 1초 대기)
    std::this_thread::sleep_for(milliseconds(1000));
    
    // 종료 시간 기록
    auto end = steady_clock::now();
    
    // 경과 시간 계산
    auto duration = end - start;
    
    // 다양한 단위로 출력
    auto ns = duration_cast<nanoseconds>(duration);
    auto ms = duration_cast<milliseconds>(duration);
    auto sec = duration_cast<seconds>(duration);
    
    std::cout << "경과 시간: " << ns.count() << "ns, " 
              << ms.count() << "ms, " 
              << sec.count() << "초" << std::endl;
    
    return 0;
}

함수 실행 시간 측정

#include <iostream>
#include <chrono>
#include <vector>
#include <algorithm>

// 측정할 함수
void heavy_computation() {
    std::vector<int> vec(1000000);
    std::iota(vec.begin(), vec.end(), 1);
    std::sort(vec.begin(), vec.end(), std::greater<int>());
}

int main() {
    using namespace std::chrono;
    
    auto start = high_resolution_clock::now();
    heavy_computation();
    auto end = high_resolution_clock::now();
    
    auto duration = duration_cast<microseconds>(end - start);
    std::cout << "함수 실행 시간: " << duration.count() << " 마이크로초" << std::endl;
    
    return 0;
}

시간 측정 클래스

재사용 가능한 시간 측정 클래스를 만들 수 있다:

#include <iostream>
#include <chrono>

class Timer {
private:
    std::chrono::steady_clock::time_point start_time;
    
public:
    Timer() : start_time(std::chrono::steady_clock::now()) {}
    
    void reset() {
        start_time = std::chrono::steady_clock::now();
    }
    
    double elapsed_seconds() const {
        auto end_time = std::chrono::steady_clock::now();
        auto duration = end_time - start_time;
        return std::chrono::duration<double>(duration).count();
    }
    
    long long elapsed_milliseconds() const {
        auto end_time = std::chrono::steady_clock::now();
        return std::chrono::duration_cast<std::chrono::milliseconds>(
            end_time - start_time).count();
    }
};

int main() {
    Timer timer;
    
    // 어떤 작업 수행
    std::this_thread::sleep_for(std::chrono::milliseconds(500));
    
    std::cout << "경과 시간: " << timer.elapsed_seconds() << "초" << std::endl;
    std::cout << "경과 시간: " << timer.elapsed_milliseconds() << "ms" << std::endl;
    
    return 0;
}

고급 사용법

Duration 산술 연산

#include <iostream>
#include <chrono>

int main() {
    using namespace std::chrono;
    
    auto d1 = milliseconds(100);
    auto d2 = seconds(2);
    
    // Duration 더하기
    auto total = d1 + d2;
    std::cout << "총 시간: " << duration_cast<milliseconds>(total).count() << "ms" << std::endl;
    
    // Duration 비교
    if (d1 < d2) {
        std::cout << "d1이 d2보다 짧습니다." << std::endl;
    }
    
    // Duration 곱하기/나누기
    auto doubled = d1 * 2;
    auto half = d2 / 2;
    
    std::cout << "두 배: " << doubled.count() << "ms" << std::endl;
    std::cout << "절반: " << duration_cast<milliseconds>(half).count() << "ms" << std::endl;
    
    return 0;
}

벤치마킹 함수

#include <iostream>
#include <chrono>
#include <functional>

template<typename Func>
double benchmark(Func&& func, int iterations = 1) {
    using namespace std::chrono;
    
    auto start = steady_clock::now();
    
    for (int i = 0; i < iterations; ++i) {
        func();
    }
    
    auto end = steady_clock::now();
    auto duration = duration_cast<nanoseconds>(end - start);
    
    return static_cast<double>(duration.count()) / iterations;
}

int main() {
    auto result = benchmark([]() {
        volatile int sum = 0;
        for (int i = 0; i < 1000; ++i) {
            sum += i;
        }
    }, 10000);
    
    std::cout << "평균 실행 시간: " << result << " 나노초" << std::endl;
    
    return 0;
}

성능 측정 시 주의사항

컴파일러 최적화

성능 측정 시 컴파일러의 최적화로 인해 측정하려는 코드가 제거될 수 있다. 이를 방지하기 위해 다음과 같은 방법을 사용할 수 있다:

// volatile 키워드 사용
volatile int result = expensive_calculation();

// 컴파일러 barrier 사용 (GCC/Clang)
asm volatile("" ::: "memory");

// std::atomic 사용
std::atomic<int> atomic_result{0};
atomic_result.store(expensive_calculation());

시계 선택

  • 성능 측정: steady_clock 사용 권장
  • 실제 시간: system_clock 사용
  • 가장 높은 정밀도: high_resolution_clock 사용

측정 정확도

매우 짧은 시간을 측정할 때는 측정 오버헤드를 고려해야 한다. 여러 번 실행하여 평균을 구하는 것이 좋다.

실용적인 예제

프로파일링 매크로

#include <iostream>
#include <chrono>

#define PROFILE_SCOPE(name) ProfileTimer timer(name)
#define PROFILE_FUNCTION() PROFILE_SCOPE(__FUNCTION__)

class ProfileTimer {
private:
    const char* name;
    std::chrono::steady_clock::time_point start;

public:
    ProfileTimer(const char* name) : name(name), start(std::chrono::steady_clock::now()) {}
    
    ~ProfileTimer() {
        auto end = std::chrono::steady_clock::now();
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
        std::cout << "[PROFILE] " << name << ": " << duration.count() << "μs" << std::endl;
    }
};

void slow_function() {
    PROFILE_FUNCTION();
    
    // 시뮬레이션된 작업
    std::this_thread::sleep_for(std::chrono::milliseconds(100));
}

int main() {
    {
        PROFILE_SCOPE("Main Block");
        slow_function();
        
        {
            PROFILE_SCOPE("Inner Block");
            std::this_thread::sleep_for(std::chrono::milliseconds(50));
        }
    }
    
    return 0;
}

타임아웃 구현

#include <iostream>
#include <chrono>
#include <thread>

class Timeout {
private:
    std::chrono::steady_clock::time_point start;
    std::chrono::steady_clock::duration limit;

public:
    template<typename Rep, typename Period>
    Timeout(const std::chrono::duration<Rep, Period>& timeout_duration) 
        : start(std::chrono::steady_clock::now()), limit(timeout_duration) {}
    
    bool expired() const {
        return (std::chrono::steady_clock::now() - start) >= limit;
    }
    
    std::chrono::milliseconds remaining() const {
        auto elapsed = std::chrono::steady_clock::now() - start;
        auto remaining_duration = limit - elapsed;
        return std::chrono::duration_cast<std::chrono::milliseconds>(remaining_duration);
    }
};

int main() {
    Timeout timeout(std::chrono::seconds(5));
    
    while (!timeout.expired()) {
        std::cout << "남은 시간: " << timeout.remaining().count() << "ms" << std::endl;
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
    }
    
    std::cout << "타임아웃!" << std::endl;
    return 0;
}

시간 변환과 형식화

시간 단위 변환

#include <iostream>
#include <chrono>

int main() {
    using namespace std::chrono;
    
    // 다양한 단위로 시간 생성
    auto ns = nanoseconds(1234567890);
    
    // 상위 단위로 변환
    auto ms = duration_cast<milliseconds>(ns);
    auto sec = duration_cast<seconds>(ns);
    
    std::cout << ns.count() << " 나노초는" << std::endl;
    std::cout << ms.count() << " 밀리초 또는" << std::endl;
    std::cout << sec.count() << " 초입니다." << std::endl;
    
    // 부동소수점 변환
    duration<double, std::milli> precise_ms = ns;
    std::cout << "정확한 밀리초: " << precise_ms.count() << "ms" << std::endl;
    
    return 0;
}

시간 형식화

#include <iostream>
#include <chrono>
#include <iomanip>
#include <sstream>

std::string format_duration(std::chrono::nanoseconds ns) {
    using namespace std::chrono;
    
    auto hours = duration_cast<std::chrono::hours>(ns);
    ns -= hours;
    auto minutes = duration_cast<std::chrono::minutes>(ns);
    ns -= minutes;
    auto seconds = duration_cast<std::chrono::seconds>(ns);
    ns -= seconds;
    auto milliseconds = duration_cast<std::chrono::milliseconds>(ns);
    
    std::stringstream ss;
    ss << std::setfill('0') << std::setw(2) << hours.count() << ":"
       << std::setw(2) << minutes.count() << ":"
       << std::setw(2) << seconds.count() << "."
       << std::setw(3) << milliseconds.count();
    
    return ss.str();
}

int main() {
    auto duration = std::chrono::nanoseconds(3723456789000); // 1시간 2분 3초 456ms
    std::cout << "형식화된 시간: " << format_duration(duration) << std::endl;
    
    return 0;
}

플랫폼별 고려사항

Windows

Windows에서는 QueryPerformanceCounter를 기반으로 하는 고해상도 타이머를 사용한다. 일반적으로 마이크로초 단위의 정밀도를 제공한다.

Linux/Unix

Linux에서는 clock_gettime 시스템 콜을 사용하며, CLOCK_MONOTONIC을 통해 단조 증가하는 시간을 제공한다.

성능 특성

  • system_clock: 중간 정도의 오버헤드, 시스템 시간 조정 영향 받음
  • steady_clock: 낮은 오버헤드, 단조 증가 보장
  • high_resolution_clock: 플랫폼에 따라 다름

활용 분야

  • 성능 분석: 함수나 알고리즘의 실행 시간 측정
  • 프로파일링: 코드의 병목 지점 찾기
  • 타임아웃 구현: 일정 시간 후 작업 중단
  • 애니메이션: 프레임 타이밍 제어
  • 벤치마킹: 다양한 구현의 성능 비교
  • 실시간 시스템: 정확한 타이밍 제어

참고 자료

각주

  1. 한 틱이 1초이면 1초 간격으로 측정한다는 것이다. 틱의 간격이 클수록 부정확해진다. 예를 들어 한 틱이 1초이면 1.4초에 실행된 코드는 1초로 취급된다.

같이 보기

최근 바뀜

더 보기