C++ Chrono 상세 가이드 | "시간 라이브러리" 가이드

C++ Chrono 상세 가이드 | "시간 라이브러리" 가이드

이 글의 핵심

std::chrono는 duration·time_point·clock으로 시간 간격과 시각을 타입 안전하게 다루는 C++11 라이브러리입니다. 이 글에서는 단위 변환, steady_clock과 system_clock 선택, 측정·타임아웃 코드 작성법을 예제와 함께 다룹니다.

들어가며

**std::chrono**는 C++11에서 도입된 타입 안전한 시간 처리 라이브러리입니다. duration(시간 간격), time_point(시간 지점), clock(시계)의 세 가지 핵심 개념으로 구성됩니다.


1. chrono 기본 구성

핵심 개념

#include <chrono>
#include <iostream>

int main() {
    // 1. duration: 시간 간격 (시간의 길이)
    // std::chrono::seconds: 초 단위 duration 타입
    std::chrono::seconds sec(10);  // 10초
    // std::chrono::milliseconds: 밀리초 단위 duration 타입
    std::chrono::milliseconds ms(1000);  // 1000밀리초 = 1초
    // 다른 단위: hours, minutes, microseconds, nanoseconds
    
    // 2. time_point: 시간 지점 (특정 시각)
    // std::chrono::system_clock::now(): 현재 시스템 시간
    // time_point는 epoch(1970-01-01 00:00:00)부터의 duration
    auto now = std::chrono::system_clock::now();
    
    // 3. clock: 시계 (시간을 측정하는 방법)
    // - system_clock: 시스템 시간 (벽시계 시간, 조정 가능)
    //   달력 시간과 변환 가능, 시스템 시간 변경 시 영향 받음
    // - steady_clock: 단조 증가 (절대 뒤로 가지 않음)
    //   시간 측정에 적합, 시스템 시간 변경 영향 없음
    // - high_resolution_clock: 고정밀 (가장 정밀한 시계)
    //   보통 steady_clock의 별칭
    
    return 0;
}

Clock 종류

Clock특징사용 시기
system_clock시스템 시간, 조정 가능현재 시각, 타임스탬프
steady_clock단조 증가, 조정 불가성능 측정, 타이머
high_resolution_clock최고 정밀도정밀 측정

2. 시간 측정

기본 측정

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

int main() {
    // using namespace: std::chrono 생략 가능
    using namespace std::chrono;
    
    // 시작 시간: 현재 시각 기록
    // high_resolution_clock::now(): 가장 정밀한 시계로 현재 시각 측정
    // time_point 타입 반환 (특정 시각)
    auto start = high_resolution_clock::now();
    
    // 작업 수행: 측정할 코드
    for (int i = 0; i < 1000000; ++i) {
        // volatile: 컴파일러 최적화 방지 (루프가 제거되지 않도록)
        volatile int x = i * i;
    }
    
    // 종료 시간: 작업 완료 후 시각 기록
    auto end = high_resolution_clock::now();
    
    // 경과 시간: end - start
    // end - start: duration 타입 (시간 간격)
    // duration_cast<milliseconds>: 밀리초 단위로 변환
    //   원래 단위(나노초 등)에서 밀리초로 형변환
    auto duration = duration_cast<milliseconds>(end - start);
    
    // count(): duration의 숫자 값 추출
    std::cout << "실행 시간: " << duration.count() << "ms" << std::endl;
    
    return 0;
}

출력:

실행 시간: 15ms

Timer 클래스

#include <chrono>
#include <iostream>

// Timer 클래스: RAII 패턴으로 자동 시간 측정
class Timer {
    // time_point: 특정 시각을 저장하는 타입
    std::chrono::time_point<std::chrono::high_resolution_clock> start;
    std::string name;
    
public:
    // 생성자: 타이머 시작
    // 초기화 리스트로 start를 현재 시각으로 설정
    Timer(const std::string& name = "Timer") 
        : start(std::chrono::high_resolution_clock::now()), name(name) {}
    
    // 소멸자: 타이머 종료 (객체 소멸 시 자동 호출)
    // RAII: 객체 생명주기와 함께 자동으로 시간 측정
    ~Timer() {
        auto end = std::chrono::high_resolution_clock::now();
        // duration_cast<microseconds>: 마이크로초 단위로 변환
        auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
        // count(): duration의 숫자 값 추출
        std::cout << name << " 경과: " << duration.count() << "μs" << std::endl;
    }
    
    // 중간 측정: 타이머를 종료하지 않고 중간 경과 시간 출력
    // lap: 랩 타임 (구간 시간)
    void lap() {
        auto now = std::chrono::high_resolution_clock::now();
        // start부터 현재까지의 경과 시간 계산
        auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(now - start);
        std::cout << name << " 랩: " << duration.count() << "ms" << std::endl;
    }
};

void processData() {
    // Timer 객체 생성: 이 시점부터 시간 측정 시작
    Timer t("processData");
    
    // 작업 1
    for (int i = 0; i < 1000000; ++i) {}
    // 중간 체크: 작업1 완료 시점 출력
    t.lap();
    
    // 작업 2
    for (int i = 0; i < 2000000; ++i) {}
    // 중간 체크: 작업2 완료 시점 출력
    t.lap();
    
    // 함수 종료: Timer 소멸자 자동 호출 → 전체 경과 시간 출력
}

int main() {
    processData();
    return 0;
}

출력:

processData 랩: 5ms
processData 랩: 15ms
processData 경과: 15234μs

3. duration (시간 간격)

기본 사용

#include <chrono>
#include <iostream>

int main() {
    using namespace std::chrono;
    
    // 시간 간격 생성: duration 타입
    // std::chrono::seconds: 초 단위 duration
    seconds sec(10);  // 10초
    // std::chrono::milliseconds: 밀리초 단위 duration
    milliseconds ms(10000);  // 10000밀리초 = 10초
    // std::chrono::minutes: 분 단위 duration
    minutes min(1);  // 1분 = 60초
    // std::chrono::hours: 시간 단위 duration
    hours hr(1);  // 1시간 = 60분 = 3600초
    
    // 값 얻기: count() 메서드로 숫자 값 추출
    std::cout << "초: " << sec.count() << std::endl;  // 10
    std::cout << "밀리초: " << ms.count() << std::endl;  // 10000
    
    return 0;
}

시간 변환

#include <chrono>
#include <iostream>

int main() {
    using namespace std::chrono;
    
    seconds s(10);  // 10초
    
    // 상위 → 하위 (암시적 변환 가능)
    // 큰 단위 → 작은 단위: 정보 손실 없음
    // seconds → milliseconds: 10초 = 10000밀리초
    milliseconds ms = s;  // 10000ms (자동 변환)
    
    std::cout << "밀리초: " << ms.count() << std::endl;  // 10000
    
    // 하위 → 상위 (명시적 duration_cast 필요)
    // 작은 단위 → 큰 단위: 정보 손실 가능
    milliseconds ms2(1500);  // 1500밀리초 = 1.5초
    // duration_cast<seconds>: 명시적 변환 (소수점 절삭)
    seconds s2 = duration_cast<seconds>(ms2);  // 1s (0.5초 버림)
    
    std::cout << "초: " << s2.count() << std::endl;  // 1
    
    return 0;
}

시간 연산

#include <chrono>
#include <iostream>

int main() {
    using namespace std::chrono;
    
    seconds s1(10);  // 10초
    seconds s2(5);   // 5초
    
    // 덧셈: duration + duration
    auto sum = s1 + s2;   // 15s
    // 뺄셈: duration - duration
    auto diff = s1 - s2;  // 5s
    // 곱셈: duration * 스칼라
    auto mul = s1 * 2;    // 20s (10초 * 2)
    // 나눗셈: duration / 스칼라
    auto div = s1 / 2;    // 5s (10초 / 2)
    
    std::cout << "합: " << sum.count() << "s" << std::endl;
    std::cout << "차: " << diff.count() << "s" << std::endl;
    std::cout << "곱: " << mul.count() << "s" << std::endl;
    std::cout << "나눔: " << div.count() << "s" << std::endl;
    
    // 비교: duration끼리 비교 가능
    // <, >, <=, >=, ==, != 모두 지원
    if (s1 > s2) {
        std::cout << "s1이 더 김" << std::endl;
    }
    
    return 0;
}

4. time_point (시간 지점)

현재 시간

#include <chrono>
#include <iostream>

int main() {
    using namespace std::chrono;
    
    // 현재 시간: time_point 타입
    // system_clock::now(): 현재 시스템 시간 (벽시계 시간)
    auto now = system_clock::now();
    
    // epoch 이후 시간: Unix epoch (1970-01-01 00:00:00 UTC)부터의 시간
    // time_since_epoch(): time_point를 duration으로 변환
    //   현재 시각 - epoch = 경과 시간
    auto epoch = now.time_since_epoch();
    // duration_cast<milliseconds>: 밀리초 단위로 변환
    auto ms = duration_cast<milliseconds>(epoch);
    
    // count(): 밀리초 값 추출 (1970년 1월 1일부터의 밀리초)
    std::cout << "epoch 이후: " << ms.count() << "ms" << std::endl;
    // 예: 1743000000000ms (약 55년)
    
    return 0;
}

시간 연산

#include <chrono>
#include <iostream>

int main() {
    using namespace std::chrono;
    
    auto now = system_clock::now();
    
    // 1시간 후
    auto future = now + hours(1);
    
    // 30분 전
    auto past = now - minutes(30);
    
    // 시간 차이
    auto diff = future - now;
    auto hours_diff = duration_cast<hours>(diff);
    
    std::cout << "시간 차이: " << hours_diff.count() << "시간" << std::endl;
    
    return 0;
}

5. 실전 예제

예제 1: 벤치마크

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

template<typename Func>
void benchmark(const std::string& name, Func func) {
    using namespace std::chrono;
    
    auto start = high_resolution_clock::now();
    func();
    auto end = high_resolution_clock::now();
    
    auto duration = duration_cast<microseconds>(end - start);
    std::cout << name << ": " << duration.count() << "μs" << std::endl;
}

int main() {
    std::vector<int> v(1000000);
    
    benchmark("생성", [&]() {
        std::generate(v.begin(), v.end(), std::rand);
    });
    
    benchmark("정렬", [&]() {
        std::sort(v.begin(), v.end());
    });
    
    benchmark("검색", [&]() {
        std::binary_search(v.begin(), v.end(), 500000);
    });
    
    return 0;
}

출력:

생성: 45231μs
정렬: 123456μs
검색: 12μs

예제 2: 타임아웃

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

int longTask() {
    std::this_thread::sleep_for(std::chrono::seconds(2));
    return 42;
}

int main() {
    using namespace std::chrono;
    
    auto future = std::async(std::launch::async, longTask);
    
    // 1초 대기
    auto status = future.wait_for(seconds(1));
    
    if (status == std::future_status::timeout) {
        std::cout << "타임아웃!" << std::endl;
    } else {
        std::cout << "결과: " << future.get() << std::endl;
    }
    
    return 0;
}

출력:

타임아웃!

예제 3: 주기적 실행

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

void periodicTask() {
    using namespace std::chrono;
    
    auto nextRun = steady_clock::now();
    const auto interval = seconds(1);
    
    for (int i = 0; i < 5; ++i) {
        nextRun += interval;
        
        // 작업 수행
        std::cout << "작업 " << i + 1 << std::endl;
        
        // 다음 실행까지 대기
        std::this_thread::sleep_until(nextRun);
    }
}

int main() {
    periodicTask();
    return 0;
}

6. chrono 리터럴 (C++14)

리터럴 사용

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

int main() {
    using namespace std::chrono_literals;
    
    // 리터럴로 duration 생성
    auto d1 = 10s;      // seconds
    auto d2 = 500ms;    // milliseconds
    auto d3 = 100us;    // microseconds
    auto d4 = 1min;     // minutes
    auto d5 = 2h;       // hours
    
    // 조합
    auto total = 1h + 30min + 45s;
    
    std::cout << "총 시간: " 
              << std::chrono::duration_cast<std::chrono::seconds>(total).count() 
              << "초" << std::endl;  // 5445초
    
    // sleep_for와 함께
    std::this_thread::sleep_for(100ms);
    
    return 0;
}

7. 자주 발생하는 문제

문제 1: 단위 변환

#include <chrono>
#include <iostream>

int main() {
    using namespace std::chrono;
    
    seconds sec(10);
    
    // ❌ 암시적 변환 안됨 (하위 → 상위)
    // milliseconds ms = sec;  // 에러
    
    // ✅ duration_cast
    auto ms = duration_cast<milliseconds>(sec);
    std::cout << "밀리초: " << ms.count() << std::endl;  // 10000
    
    return 0;
}

문제 2: 시계 선택

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

void wrongClock() {
    using namespace std::chrono;
    
    // ❌ system_clock (시스템 시간 변경 영향)
    auto start = system_clock::now();
    
    // 사용자가 시스템 시간을 변경하면?
    std::this_thread::sleep_for(seconds(1));
    
    auto end = system_clock::now();
    auto duration = duration_cast<milliseconds>(end - start);
    
    // 음수일 수도 있음!
    std::cout << "경과: " << duration.count() << "ms" << std::endl;
}

void correctClock() {
    using namespace std::chrono;
    
    // ✅ steady_clock (단조 증가 보장)
    auto start = steady_clock::now();
    
    std::this_thread::sleep_for(seconds(1));
    
    auto end = steady_clock::now();
    auto duration = duration_cast<milliseconds>(end - start);
    
    // 항상 양수
    std::cout << "경과: " << duration.count() << "ms" << std::endl;
}

int main() {
    correctClock();
    return 0;
}

문제 3: 정밀도 손실

#include <chrono>
#include <iostream>

int main() {
    using namespace std::chrono;
    
    milliseconds ms(1500);
    
    // duration_cast: 절삭
    auto s1 = duration_cast<seconds>(ms);
    std::cout << "절삭: " << s1.count() << "s" << std::endl;  // 1s (500ms 손실)
    
    // C++17: round (반올림)
    auto s2 = round<seconds>(ms);
    std::cout << "반올림: " << s2.count() << "s" << std::endl;  // 2s
    
    // C++17: ceil (올림)
    auto s3 = ceil<seconds>(ms);
    std::cout << "올림: " << s3.count() << "s" << std::endl;  // 2s
    
    // C++17: floor (내림)
    auto s4 = floor<seconds>(ms);
    std::cout << "내림: " << s4.count() << "s" << std::endl;  // 1s
    
    return 0;
}

문제 4: 타입 추론

#include <chrono>
#include <iostream>

int main() {
    using namespace std::chrono;
    
    // ❌ 타입 불명확
    auto d1 = 10;  // int
    
    // ✅ 명시적 타입
    seconds d2(10);
    
    // ✅ 리터럴 (C++14)
    using namespace std::chrono_literals;
    auto d3 = 10s;
    
    std::cout << "d2: " << d2.count() << "s" << std::endl;
    std::cout << "d3: " << d3.count() << "s" << std::endl;
    
    return 0;
}

8. 실전 예제: 성능 프로파일러

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

class Profiler {
    using Clock = std::chrono::high_resolution_clock;
    using TimePoint = std::chrono::time_point<Clock>;
    
    std::map<std::string, TimePoint> starts;
    std::map<std::string, long long> totals;  // 마이크로초
    
public:
    void start(const std::string& name) {
        starts[name] = Clock::now();
    }
    
    void stop(const std::string& name) {
        auto end = Clock::now();
        auto it = starts.find(name);
        
        if (it != starts.end()) {
            auto duration = std::chrono::duration_cast<std::chrono::microseconds>(
                end - it->second
            );
            totals[name] += duration.count();
            starts.erase(it);
        }
    }
    
    void report() {
        std::cout << "\n=== 프로파일링 결과 ===" << std::endl;
        
        for (const auto& [name, total] : totals) {
            if (total < 1000) {
                std::cout << name << ": " << total << "μs" << std::endl;
            } else if (total < 1000000) {
                std::cout << name << ": " << (total / 1000.0) << "ms" << std::endl;
            } else {
                std::cout << name << ": " << (total / 1000000.0) << "s" << std::endl;
            }
        }
    }
};

void algorithm1() {
    for (int i = 0; i < 1000000; ++i) {}
}

void algorithm2() {
    for (int i = 0; i < 2000000; ++i) {}
}

int main() {
    Profiler profiler;
    
    // 알고리즘 1 측정
    profiler.start("algorithm1");
    algorithm1();
    profiler.stop("algorithm1");
    
    // 알고리즘 2 측정
    profiler.start("algorithm2");
    algorithm2();
    profiler.stop("algorithm2");
    
    // 여러 번 실행
    for (int i = 0; i < 5; ++i) {
        profiler.start("algorithm1");
        algorithm1();
        profiler.stop("algorithm1");
    }
    
    profiler.report();
    
    return 0;
}

출력:

=== 프로파일링 결과 ===
algorithm1: 25.5ms
algorithm2: 8.3ms

정리

핵심 요약

  1. chrono: C++11 시간 라이브러리
  2. duration: 시간 간격 (seconds, milliseconds 등)
  3. time_point: 시간 지점
  4. clock: system_clock, steady_clock, high_resolution_clock
  5. duration_cast: 단위 변환
  6. 리터럴: 10s, 500ms (C++14)

Clock 선택 가이드

목적Clock이유
성능 측정steady_clock단조 증가
타이머steady_clock시간 조정 영향 없음
현재 시각system_clock시스템 시간
타임스탬프system_clockUnix epoch 변환
정밀 측정high_resolution_clock최고 정밀도

실전 팁

사용 원칙:

  • 성능 측정은 steady_clock
  • 현재 시각은 system_clock
  • 하위→상위 변환은 duration_cast
  • 리터럴 사용 (10s, 500ms)

성능:

  • steady_clock이 가장 빠름
  • duration_cast는 컴파일 타임
  • 리터럴은 가독성 향상
  • Timer 클래스로 RAII 활용

주의사항:

  • system_clock은 시간 조정 영향
  • duration_cast는 절삭 (정밀도 손실)
  • 하위→상위 변환은 명시적
  • 음수 duration 가능

다음 단계

  • C++ Duration
  • C++ Time Point
  • C++ Timer Utilities

관련 글

  • C++ duration |
  • C++ Chrono 완벽 가이드 |
  • C++ Chrono Literals |
  • C++ ratio |
  • C++ steady_clock |