개요
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: 플랫폼에 따라 다름
활용 분야
- 성능 분석: 함수나 알고리즘의 실행 시간 측정
- 프로파일링: 코드의 병목 지점 찾기
- 타임아웃 구현: 일정 시간 후 작업 중단
- 애니메이션: 프레임 타이밍 제어
- 벤치마킹: 다양한 구현의 성능 비교
- 실시간 시스템: 정확한 타이밍 제어
참고 자료
- C++ Reference - Chrono
- CPlusPlus.com - Chrono
- ISO/IEC 14882:2011 (C++11 표준)
각주
- ↑ 한 틱이 1초이면 1초 간격으로 측정한다는 것이다. 틱의 간격이 클수록 부정확해진다. 예를 들어 한 틱이 1초이면 1.4초에 실행된 코드는 1초로 취급된다.