time() 대신 std::chrono: duration·time_point, 클럭 선택, duration_cast, C++20 달력
들어가며: “함수 실행 시간이 매번 달라요”
time()으로 측정했는데 결과가 신뢰할 수 없다
벤치마크 코드를 작성했습니다. time()이나 clock()으로 함수 실행 시간을 측정했는데, 실행할 때마다 결과가 들쭉날쭉하다. 시스템 시계가 NTP로 조정되거나, clock()이 CPU 시간만 측정해서 멀티스레드 환경에서 부정확합니다.
문제의 코드:
// ❌ 나쁜 예: time()은 1초 단위라 짧은 시간 측정 불가
#include <ctime>
void benchmark() {
time_t start = time(nullptr);
doSomething();
time_t end = time(nullptr);
printf("Elapsed: %ld sec\n", end - start); // 1초 미만이면 0
}
// ❌ 나쁜 예: clock()은 CPU 시간이라 sleep 중에는 증가 안 함
#include <ctime>
void benchmark() {
clock_t start = clock();
doSomething(); // sleep 포함 시 부정확
clock_t end = clock();
printf("CPU: %f sec\n", (double)(end - start) / CLOCKS_PER_SEC);
}
chrono로 해결:
// ✅ 좋은 예: steady_clock은 단조 증가, 나노초 단위
#include <chrono>
#include <iostream>
void benchmark() {
auto start = std::chrono::steady_clock::now();
doSomething();
auto end = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
std::cout << "Elapsed: " << elapsed.count() << " ms\n";
}
이 글을 읽으면:
std::chrono의 duration, time_point, clocks를 이해할 수 있습니다.duration_cast로 단위 변환을 할 수 있습니다.- 벤치마크, 타임아웃, 로그 타임스탬프 등 실전 패턴을 적용할 수 있습니다.
- 자주 발생하는 에러와 프로덕션 패턴을 알 수 있습니다.
실행마다 다른 벤치마크, NTP에 꼬이는 타임아웃: chrono가 필요한 상황
시나리오 1: 벤치마크 결과가 실행마다 다르다
문제: 알고리즘 A와 B의 성능을 비교하는데, time(nullptr)로 측정하면 둘 다 0초로 나옵니다. 1초 단위라 밀리초 단위 차이를 잡을 수 없습니다.
// ❌ 문제: 1초 미만 실행 시 항상 0
time_t t1 = time(nullptr);
algorithmA();
time_t t2 = time(nullptr);
printf("A: %ld sec\n", t2 - t1); // 0.05초 걸려도 0 출력
해결: std::chrono::steady_clock과 duration_cast로 밀리초·마이크로초 단위 측정.
// ✅ 해결
#include <chrono>
auto start = std::chrono::steady_clock::now();
algorithmA();
auto end = std::chrono::steady_clock::now();
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
std::cout << "A: " << ms.count() << " ms\n";
시나리오 2: 타임아웃 계산이 NTP 조정 시 꼬인다
문제: “30초 후에 타임아웃”을 system_clock::now() + 30초로 계산했는데, 실행 중 NTP가 시스템 시계를 1분 뒤로 되돌리면 타임아웃이 영원히 오지 않습니다.
// ❌ 문제: system_clock은 시스템 시계 변경에 영향받음
auto deadline = std::chrono::system_clock::now() + std::chrono::seconds(30);
while (std::chrono::system_clock::now() < deadline) {
doWork(); // NTP 조정 시 deadline이 과거가 되어 무한 루프 가능
}
해결: 타임아웃에는 steady_clock을 사용합니다. steady_clock은 단조 증가하므로 NTP 영향 없음.
// ✅ 해결
auto start = std::chrono::steady_clock::now();
auto timeout = std::chrono::seconds(30);
while (std::chrono::steady_clock::now() - start < timeout) {
doWork();
}
시나리오 3: 로그에 사람이 읽을 수 있는 시간을 찍고 싶다
문제: steady_clock::now()는 “부팅 후 경과 시간”이라 사람이 읽기 어렵습니다. “2026-03-11 14:30:00” 형태로 출력하려면 system_clock과 포맷 변환이 필요합니다.
// ❌ steady_clock은 "부팅 후 나노초" 같은 값
auto tp = std::chrono::steady_clock::now();
// 사람이 읽을 수 있는 형식으로 변환 불가
// ✅ system_clock은 실제 날짜/시간과 대응
auto tp = std::chrono::system_clock::now();
// C++20 chrono 또는 localtime으로 포맷 가능
시나리오 4: duration 단위가 달라서 컴파일 에러
문제: std::chrono::seconds와 std::chrono::milliseconds를 직접 더하거나 비교하려 하면 타입이 달라 컴파일 에러가 납니다.
// ❌ 문제: 서로 다른 duration 타입
auto s = std::chrono::seconds(1);
auto ms = std::chrono::milliseconds(500);
auto sum = s + ms; // ✅ C++11부터는 공통 타입으로 자동 변환되어 OK
// 하지만 특정 단위로 캐스팅할 때는 duration_cast 필요
시나리오 5: Asio deadline_timer에 chrono duration 전달
문제: Boost.Asio나 std::experimental::net의 async_wait에는 chrono::duration을 직접 넘겨야 합니다. int 초를 넘기면 타입 에러가 납니다.
// ❌ 문제
timer.expires_after(30); // 30이 뭔지? 초? 밀리초?
// ✅ 해결
timer.expires_after(std::chrono::seconds(30));
시나리오 6: “다음 월요일” 같은 날짜 계산이 어렵다
문제: localtime과 tm 구조체로 “다음 주 월요일”, “이번 달 마지막 날” 같은 날짜를 계산하려면 수동으로 일수·윤년·월 경계를 처리해야 합니다. 실수하기 쉽고 가독성이 떨어집니다.
// ❌ C++17 이하: 수동 날짜 계산
std::tm tm = *std::localtime(&tt);
tm.tm_mday += 7; // 7일 후? 월 넘어가면?
mktime(&tm); // 정규화 필요
해결: C++20 std::chrono::year_month_day, weekday 등 calendar 타입을 사용하면 타입 안전하고 직관적입니다.
// ✅ C++20: calendar 타입으로 명확한 날짜 연산
#include <chrono>
using namespace std::chrono;
auto today = floor<days>(system_clock::now());
year_month_day ymd = year_month_day{sys_days{today}};
// 다음 월요일: weekday_indexed 활용
duration: 시간 구간
기본 개념
std::chrono::duration은 시간의 길이를 표현합니다. 템플릿 인자로 “표현 타입(Rep)“과 “단위 비율(Ratio)“을 받습니다.
// 복사해 붙여넣은 뒤: g++ -std=c++17 -o duration_basic duration_basic.cpp && ./duration_basic
#include <chrono>
#include <iostream>
int main() {
// 기본 duration 타입들
std::chrono::nanoseconds ns(1000000000); // 10^9 나노초 = 1초
std::chrono::microseconds us(1000000); // 10^6 마이크로초 = 1초
std::chrono::milliseconds ms(1000); // 1000 밀리초 = 1초
std::chrono::seconds s(1); // 1초
std::chrono::minutes m(1); // 1분
std::chrono::hours h(1); // 1시간
std::cout << "1초 = " << ns.count() << " ns\n";
std::cout << "1초 = " << ms.count() << " ms\n";
std::cout << "1분 = " << std::chrono::duration_cast<std::chrono::seconds>(m).count() << "초\n";
// 연산
auto sum = std::chrono::seconds(30) + std::chrono::milliseconds(500);
std::cout << "30.5초 = " << std::chrono::duration_cast<std::chrono::milliseconds>(sum).count() << " ms\n";
return 0;
}
실행 결과:
1초 = 1000000000 ns
1초 = 1000 ms
1분 = 60초
30.5초 = 30500 ms
duration 내부 구조:
Rep:int64_t,double등 실제 값을 저장하는 타입Period:std::ratio<1, 1000>= 1/1000초 = 밀리초
duration 연산
#include <chrono>
int main() {
using namespace std::chrono;
auto d1 = seconds(10);
auto d2 = milliseconds(500);
// 덧셈, 뺄셈 (공통 타입으로 자동 변환)
auto sum = d1 + d2; // 10.5초
auto diff = d1 - d2; // 9.5초
// 곱셈, 나눗셈 (스칼라와)
auto doubled = d1 * 2; // 20초
auto half = d1 / 2; // 5초
// 비교
bool b = (d1 > d2); // true
// count(): 내부 값 추출
auto ms = duration_cast<milliseconds>(sum);
std::cout << ms.count() << " ms\n"; // 10500
return 0;
}
커스텀 duration
#include <chrono>
#include <iostream>
// 1/10 초 = 100ms 단위 (데시초)
using Deciseconds = std::chrono::duration<int, std::ratio<1, 10>>;
// 1/30 초 (프레임 기반 게임용)
using FrameDuration = std::chrono::duration<double, std::ratio<1, 30>>;
int main() {
Deciseconds ds(5); // 0.5초
FrameDuration fd(1); // 1프레임 = 1/30초
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(ds);
std::cout << "5 deciseconds = " << ms.count() << " ms\n";
return 0;
}
time_point: 시점
기본 개념
std::chrono::time_point는 특정 시점을 표현합니다. “어떤 clock의 epoch(기준 시점)으로부터 얼마나 지났는지”를 duration으로 저장합니다.
// 복사해 붙여넣은 뒤: g++ -std=c++17 -o timepoint_basic timepoint_basic.cpp && ./timepoint_basic
#include <chrono>
#include <iostream>
int main() {
// 현재 시점
auto now_sys = std::chrono::system_clock::now();
auto now_steady = std::chrono::steady_clock::now();
// time_point + duration = time_point
auto future = now_sys + std::chrono::hours(24);
auto past = now_sys - std::chrono::seconds(3600);
// time_point - time_point = duration
auto elapsed = now_steady - (now_steady - std::chrono::milliseconds(100));
std::cout << "Elapsed: " << elapsed.count() << " (steady_clock 단위)\n";
// system_clock::time_point -> time_t (C API 호환)
auto tt = std::chrono::system_clock::to_time_t(now_sys);
std::cout << "time_t: " << tt << "\n";
return 0;
}
time_point 연산
#include <chrono>
int main() {
using namespace std::chrono;
auto tp = steady_clock::now();
// 시점 + 구간 = 시점
auto later = tp + seconds(10);
auto earlier = tp - milliseconds(500);
// 시점 - 시점 = 구간
auto diff = later - tp; // 10초
auto ms = duration_cast<milliseconds>(diff);
std::cout << ms.count() << " ms\n";
return 0;
}
clocks: system_clock, steady_clock, high_resolution_clock
세 가지 표준 클럭
flowchart TB
subgraph system[system_clock]
S1[실제 날짜/시간]
S2[NTP 영향 받음]
S3[to_time_t 가능]
end
subgraph steady[steady_clock]
T1[단조 증가]
T2[NTP 영향 없음]
T3[벤치마크·타임아웃]
end
subgraph high[high_resolution_clock]
H1[가장 높은 해상도]
H2[구현에 따라 steady일 수도]
end
| 클럭 | 용도 | NTP 영향 | 단조 증가 |
|---|---|---|---|
system_clock | 실제 날짜/시간, 로그 타임스탬프 | 있음 | 아니오 |
steady_clock | 벤치마크, 타임아웃, 경과 시간 | 없음 | 예 |
high_resolution_clock | 최고 해상도 필요 시 | 구현 의존 | 구현 의존 |
system_clock
#include <chrono>
#include <ctime>
#include <iostream>
int main() {
auto now = std::chrono::system_clock::now();
// time_t로 변환 후 localtime
auto tt = std::chrono::system_clock::to_time_t(now);
std::tm* tm = std::localtime(&tt);
char buf[64];
std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", tm);
std::cout << "현재 시간: " << buf << "\n";
// from_time_t: time_t -> time_point
auto tp = std::chrono::system_clock::from_time_t(tt);
return 0;
}
steady_clock
#include <chrono>
#include <iostream>
#include <thread>
int main() {
auto start = std::chrono::steady_clock::now();
std::this_thread::sleep_for(std::chrono::milliseconds(100));
auto end = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
std::cout << "Slept: " << elapsed.count() << " ms\n";
// steady_clock은 to_time_t 없음 (실제 날짜와 무관)
// auto tt = std::chrono::steady_clock::to_time_t(start); // ❌ 컴파일 에러
return 0;
}
high_resolution_clock
#include <chrono>
#include <iostream>
int main() {
// libstdc++(GCC): high_resolution_clock은 system_clock의 별칭 (is_steady == false)
// MSVC STL: steady_clock의 별칭 / libc++(Clang): 설정에 따라 다름
auto start = std::chrono::high_resolution_clock::now();
volatile int x = 0;
for (int i = 0; i < 1000; ++i) x += i;
auto end = std::chrono::high_resolution_clock::now();
auto ns = std::chrono::duration_cast<std::chrono::nanoseconds>(end - start);
std::cout << "Loop: " << ns.count() << " ns\n";
return 0;
}
주의: high_resolution_clock이 steady인지 구현에 따라 다릅니다. GCC 10(libstdc++)에서 std::is_same_v<high_resolution_clock, system_clock>은 true, high_resolution_clock::is_steady는 false로 확인했습니다. 즉 GCC에서는 이 시계로 잰 경과 시간이 NTP 보정이나 사용자의 시각 변경에 따라 뒤로 갈 수 있고, “가장 정밀한 시계”라는 이름과 달리 system_clock보다 정밀하지도 않습니다. 인터넷 예제에 흔한 high_resolution_clock::now() 벤치마크는 대부분 문제없이 돌지만, 경과 시간 측정에는 이름보다 보장이 명확한 steady_clock을 쓰는 것이 정석입니다.
duration_cast와 단위 변환
duration_cast
서로 다른 단위의 duration을 변환할 때 duration_cast를 사용합니다. 잘림(truncation)이 발생할 수 있습니다.
#include <chrono>
#include <iostream>
int main() {
using namespace std::chrono;
// 1500ms -> 1초 (잘림)
milliseconds ms(1500);
seconds s = duration_cast<seconds>(ms);
std::cout << "1500 ms = " << s.count() << " sec (truncated)\n";
// 1.5초 -> 1500ms
duration<double> sec(1.5);
auto ms2 = duration_cast<milliseconds>(sec);
std::cout << "1.5 sec = " << ms2.count() << " ms\n";
// floor, ceil, round (C++17)
auto sec_floor = floor<seconds>(ms); // 1초
auto sec_ceil = ceil<seconds>(ms); // 2초
auto sec_round = round<seconds>(ms); // 2초 (1.5는 반올림)
std::cout << "floor: " << sec_floor.count() << ", ceil: " << sec_ceil.count()
<< ", round: " << sec_round.count() << "\n";
return 0;
}
floor, ceil, round (C++17)
#include <chrono>
#include <iostream>
int main() {
using namespace std::chrono;
milliseconds ms(1250);
// floor: 내림
auto s_floor = floor<seconds>(ms); // 1초
// ceil: 올림
auto s_ceil = ceil<seconds>(ms); // 2초
// round: 반올림
auto s_round = round<seconds>(ms); // 1초 (1250ms는 1.25초, 반올림 시 1초)
std::cout << "1250 ms -> floor: " << s_floor.count()
<< "s, ceil: " << s_ceil.count()
<< "s, round: " << s_round.count() << "s\n";
return 0;
}
C++20 Calendar·날짜 연산
C++20부터 <chrono>에 calendar와 timezone 확장이 추가되었습니다. year, month, day, weekday, year_month_day 등 타입으로 날짜를 타입 안전하게 다룰 수 있습니다.
year_month_day
#include <chrono>
#include <iostream>
#if __cplusplus >= 202002L
int main() {
using namespace std::chrono;
// 2026년 3월 11일
year_month_day ymd{year(2026), month(3), day(11)};
// 유효성 검사
if (ymd.ok()) {
std::cout << "Valid date: " << ymd << "\n";
}
// system_clock::time_point와 변환
auto tp = sys_days{ymd};
auto ymd2 = year_month_day{floor<days>(tp)};
// 오늘 날짜
auto today = year_month_day{floor<days>(system_clock::now())};
std::cout << "Today: " << today << "\n";
return 0;
}
#endif
weekday, month_day_last (요일·월 마지막 날)
#if __cplusplus >= 202002L
#include <chrono>
#include <iostream>
int main() {
using namespace std::chrono;
year_month_day ymd{2026y, March, 11d};
std::cout << "2026-03-11: " << weekday{ymd} << "\n"; // Wednesday
// 2월 마지막 날 (윤년 자동)
year_month_day_last ymdl{year(2026), month_day_last(month(2))};
std::cout << "Last of 2026-02: " << year_month_day{ymdl} << "\n";
return 0;
}
#endif
C++20 chrono vs C++17 이하
| 기능 | C++17 이하 | C++20 |
|---|---|---|
| 날짜 표현 | time_t + tm | year_month_day |
| 요일 | tm.tm_wday 수동 | weekday 타입 |
| 월 마지막 날 | 수동 계산 | month_day_last |
| 포맷 | strftime | std::format |
| 타임존 | 수동 | std::chrono::time_zone |
실행 시간 측정, 스코프 타이머, 타임아웃 루프 예제
예제 1: 함수 실행 시간 측정
#include <chrono>
#include <functional>
#include <iostream>
#include <thread>
template <typename Func, typename... Args>
auto measure_ms(Func&& f, Args&&... args) {
auto start = std::chrono::steady_clock::now();
std::invoke(std::forward<Func>(f), std::forward<Args>(args)...);
auto end = std::chrono::steady_clock::now();
return std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
}
int main() {
auto ms = measure_ms([] { std::this_thread::sleep_for(std::chrono::milliseconds(50)); });
std::cout << "Elapsed: " << ms << " ms\n";
return 0;
}
예제 2: RAII 스코프 타이머
#include <chrono>
#include <iostream>
#include <string>
#include <thread>
class ScopedTimer {
public:
explicit ScopedTimer(const std::string& name)
: name_(name), start_(std::chrono::steady_clock::now()) {}
~ScopedTimer() {
auto end = std::chrono::steady_clock::now();
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start_).count();
std::cout << "[" << name_ << "] " << ms << " ms\n";
}
private:
std::string name_;
std::chrono::steady_clock::time_point start_;
};
void slowFunction() {
ScopedTimer timer("slowFunction");
std::this_thread::sleep_for(std::chrono::milliseconds(100));
}
int main() {
slowFunction();
return 0;
}
예제 3: 타임아웃 루프
#include <chrono>
#include <iostream>
#include <thread>
bool doWorkWithTimeout(std::chrono::milliseconds timeout) {
auto deadline = std::chrono::steady_clock::now() + timeout;
while (std::chrono::steady_clock::now() < deadline) {
if (tryDoWork()) {
return true;
}
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
return false;
}
bool tryDoWork() {
static int count = 0;
return ++count >= 5; // 5번째 시도에서 성공
}
int main() {
if (doWorkWithTimeout(std::chrono::seconds(1))) {
std::cout << "Success\n";
} else {
std::cout << "Timeout\n";
}
return 0;
}
예제 4: 로그 타임스탬프 (system_clock)
#include <chrono>
#include <ctime>
#include <iomanip>
#include <iostream>
#include <sstream>
std::string getTimestamp() {
auto now = std::chrono::system_clock::now();
auto tt = std::chrono::system_clock::to_time_t(now);
std::tm tm_buf;
#ifdef _WIN32
localtime_s(&tm_buf, &tt);
#else
localtime_r(&tt, &tm_buf);
#endif
std::ostringstream oss;
oss << std::put_time(&tm_buf, "%Y-%m-%d %H:%M:%S");
return oss.str();
}
int main() {
std::cout << "[" << getTimestamp() << "] Application started\n";
return 0;
}
예제 5: duration 산술 종합
#include <chrono>
#include <iostream>
int main() {
using namespace std::chrono;
// 다양한 단위 혼합
auto total = hours(1) + minutes(30) + seconds(45) + milliseconds(123);
std::cout << "Total: " << duration_cast<milliseconds>(total).count() << " ms\n";
// 비율 계산
auto fps = 60.0;
auto frame_time = duration<double, std::ratio<1>>(1.0 / fps);
auto frame_ms = duration_cast<milliseconds>(frame_time);
std::cout << "60 FPS = " << frame_ms.count() << " ms per frame\n";
return 0;
}
예제 6: time() vs chrono 해상도·정확도 비교
| 측정 방식 | 최소 해상도 | NTP 영향 | sleep 포함 시 | 멀티스레드 |
|---|---|---|---|---|
time() | 1초 | 있음 | 부정확 | 부정확 |
clock() | ~1ms | 없음 | CPU 시간만 (sleep 미포함) | 프로세스 전체 합산 |
steady_clock | 나노초 | 없음 | 정확 | 정확 |
system_clock | 나노초 | 있음 | 정확 | 정확 |
#include <chrono>
#include <ctime>
#include <iostream>
#include <thread>
void compareResolution() {
// time(): 50ms sleep을 0초로 측정
auto t1 = std::time(nullptr);
std::this_thread::sleep_for(std::chrono::milliseconds(50));
auto t2 = std::time(nullptr);
std::cout << "time(): " << (t2 - t1) << " sec (실제 50ms)\n";
// chrono: 정확히 50ms 측정
auto start = std::chrono::steady_clock::now();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
auto end = std::chrono::steady_clock::now();
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
std::cout << "chrono: " << ms << " ms\n";
}
실행 결과 예시:
time(): 0 sec (실제 50ms)
chrono: 50 ms
system_clock 타임아웃, duration_cast 잘림, clock 혼용 같은 에러
에러 1: system_clock을 타임아웃에 사용
증상: NTP 조정 후 타임아웃이 오지 않거나, 너무 빨리 발생합니다.
// ❌ 잘못된 사용
auto deadline = std::chrono::system_clock::now() + std::chrono::seconds(30);
while (condition && std::chrono::system_clock::now() < deadline) { ... }
해결: 타임아웃·경과 시간에는 steady_clock을 사용합니다.
// ✅ 올바른 사용
auto start = std::chrono::steady_clock::now();
auto timeout = std::chrono::seconds(30);
while (condition && (std::chrono::steady_clock::now() - start < timeout)) { ... }
에러 2: duration_cast 잘림 무시
증상: 1500ms를 seconds로 캐스팅하면 1초가 되어 0.5초가 사라집니다.
// ❌ 주의: 잘림 발생
auto ms = std::chrono::milliseconds(1500);
auto s = std::chrono::duration_cast<std::chrono::seconds>(ms);
// s.count() == 1 (0.5초 손실)
해결: duration<double, std::ratio<1>>로 부동소수 초를 쓰거나, floor/ceil/round로 의도를 명확히 합니다.
// ✅ 부동소수로 정밀도 유지
auto ms = std::chrono::milliseconds(1500);
std::chrono::duration<double> sec(ms);
// sec.count() == 1.5
// ✅ 반올림이 필요하면 round
auto s = std::chrono::round<std::chrono::seconds>(ms);
에러 3: steady_clock을 로그 타임스탬프에 사용
증상: 로그에 “부팅 후 1234567890 나노초” 같은 값이 찍혀 사람이 읽을 수 없습니다.
// ❌ steady_clock은 실제 날짜/시간 아님
auto tp = std::chrono::steady_clock::now();
// to_time_t 없음 -> localtime 불가
해결: 로그 타임스탬프에는 system_clock을 사용합니다.
// ✅ system_clock + to_time_t + strftime
auto now = std::chrono::system_clock::now();
auto tt = std::chrono::system_clock::to_time_t(now);
std::tm* tm = std::localtime(&tt);
char buf[64];
std::strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", tm);
에러 4: count()만 사용하고 단위 혼동
증상: duration.count()는 “내부 표현값”이라 단위가 duration 타입에 따라 다릅니다.
// ❌ count()의 의미가 duration 타입에 따라 다름
auto d = std::chrono::milliseconds(1500);
std::cout << d.count(); // 1500 (ms)
auto d2 = std::chrono::seconds(1);
std::cout << d2.count(); // 1 (sec)
해결: 출력 시 단위를 명시하거나, duration_cast로 원하는 단위로 맞춘 뒤 count()를 사용합니다.
// ✅ 단위 명시
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(d);
std::cout << ms.count() << " ms\n";
에러 5: 다른 clock의 time_point 혼용
증상: system_clock::time_point와 steady_clock::time_point를 빼거나 비교하면 컴파일 에러입니다.
// ❌ 컴파일 에러: 서로 다른 clock
auto tp_sys = std::chrono::system_clock::now();
auto tp_steady = std::chrono::steady_clock::now();
auto diff = tp_sys - tp_steady; // 에러!
해결: 같은 clock 내에서만 time_point 연산을 합니다. C++20에서는 clock_cast로 변환할 수 있습니다.
// ✅ 같은 clock 내에서만 연산
auto t1 = std::chrono::steady_clock::now();
// ...
auto t2 = std::chrono::steady_clock::now();
auto elapsed = t2 - t1;
에러 6: 표현 타입 범위를 넘는 duration
duration은 내부에 정수 하나(rep)를 저장하므로 범위를 넘으면 일반 정수처럼 오버플로합니다. 표준 별칭은 넉넉한 타입을 보장하지만(nanoseconds는 최소 64비트), 문제는 주로 이런 곳에서 생깁니다.
nanoseconds로 긴 기간 표현: 64비트 나노초는 약 ±292년까지만 표현합니다.system_clock의 epoch부터 계산한 값을 나노초로 바꾸는 건 괜찮지만, 먼 미래·과거 날짜를 나노초 단위로 다루면 넘칠 수 있습니다.- 직접 만든 좁은 duration:
duration<int, std::milli>는 약 24.8일에서 넘칩니다. 몇 주짜리 캐시 만료 시간을 이 타입에 담으면 음수가 됩니다. count()를int에 담기:int ms = d.count();는duration쪽은 멀쩡해도 여기서 잘립니다.
using namespace std::chrono;
duration<int, std::milli> ttl{std::numeric_limits<int>::max()}; // 약 24.8일이 한계
// auto longer = ttl + hours(24); // ❌ int 범위 초과 → 오버플로
milliseconds ttl64 = days(30); // ✅ 표준 milliseconds는 최소 45비트 rep
auto ms = ttl64.count(); // ✅ auto로 받아 rep 타입 유지 (int로 받지 않기)
용도별 clock 선택과 duration을 인자로 쓰는 습관
용도별 clock 선택
| 용도 | 권장 clock |
|---|---|
| 벤치마크, 성능 측정 | steady_clock |
| 타임아웃, 경과 시간 | steady_clock |
| 로그 타임스탬프, 실제 날짜/시간 | system_clock |
| 최고 해상도 필요 (구현 확인 후) | high_resolution_clock |
using으로 가독성 향상
using namespace std::chrono;
using namespace std::chrono_literals; // C++14
auto timeout = 30s; // 30초
auto delay = 100ms; // 100밀리초
auto deadline = steady_clock::now() + timeout;
chrono_literals (C++14)
#include <chrono>
using namespace std::chrono_literals;
auto d1 = 1s; // seconds(1)
auto d2 = 100ms; // milliseconds(100)
auto d3 = 1.5min; // duration<double, ratio<60>>(1.5)
duration을 함수 인자/반환에 사용
// ✅ 명확한 의도
void setTimeout(std::chrono::milliseconds ms);
std::chrono::milliseconds getElapsed();
// ❌ int 초는 모호함
void setTimeout(int seconds); // 초인지 ms인지 불명확
벤치마크 시 워밍업
// 첫 실행은 캐시 미스 등으로 느릴 수 있음
for (int i = 0; i < 3; ++i) {
doWork(); // 워밍업
}
auto start = std::chrono::steady_clock::now();
for (int i = 0; i < 100; ++i) {
doWork();
}
auto end = std::chrono::steady_clock::now();
auto avg = (end - start) / 100;
타이머 클래스, 캐시 만료, Asio 타이머, FPS 제한
패턴 1: 재사용 가능한 타이머 클래스
#include <chrono>
#include <functional>
#include <iostream>
class Timer {
public:
using Callback = std::function<void(std::chrono::milliseconds)>;
void start() {
start_ = std::chrono::steady_clock::now();
}
std::chrono::milliseconds elapsed() const {
auto now = std::chrono::steady_clock::now();
return std::chrono::duration_cast<std::chrono::milliseconds>(now - start_);
}
void stopAndReport(Callback cb) {
cb(elapsed());
}
private:
std::chrono::steady_clock::time_point start_;
};
int main() {
Timer t;
t.start();
std::this_thread::sleep_for(std::chrono::milliseconds(50));
t.stopAndReport([](std::chrono::milliseconds ms) {
std::cout << "Elapsed: " << ms.count() << " ms\n";
});
return 0;
}
패턴 2: 캐시 만료 시간
#include <chrono>
#include <unordered_map>
#include <optional>
template <typename K, typename V>
class TimedCache {
public:
using Clock = std::chrono::steady_clock;
using TimePoint = Clock::time_point;
using Duration = std::chrono::seconds;
explicit TimedCache(Duration ttl) : ttl_(ttl) {}
void insert(const K& key, const V& value) {
cache_[key] = {value, Clock::now()};
}
std::optional<V> get(const K& key) {
auto it = cache_.find(key);
if (it == cache_.end()) return std::nullopt;
if (Clock::now() - it->second.second > ttl_) {
cache_.erase(it);
return std::nullopt;
}
return it->second.first;
}
private:
Duration ttl_;
std::unordered_map<K, std::pair<V, TimePoint>> cache_;
};
int main() {
TimedCache<std::string, int> cache(std::chrono::seconds(60));
cache.insert("key", 42);
if (auto v = cache.get("key")) {
std::cout << "Value: " << *v << "\n";
}
return 0;
}
패턴 3: Asio 타이머와 연동
#include <chrono>
#include <boost/asio.hpp>
void runWithTimeout(boost::asio::io_context& io, std::chrono::seconds timeout) {
boost::asio::steady_timer timer(io, timeout);
timer.async_wait([](const boost::system::error_code& ec) {
if (!ec) {
std::cout << "Timeout!\n";
}
});
io.run();
}
패턴 4: FPS 제한 (프레임 타이밍)
#include <chrono>
#include <functional>
#include <thread>
void runAtFixedRate(std::chrono::milliseconds frameTime,
std::function<void()> update,
std::function<bool()> isRunning) {
auto nextFrame = std::chrono::steady_clock::now();
while (isRunning()) {
update();
nextFrame += frameTime;
std::this_thread::sleep_until(nextFrame);
}
}
// 60 FPS 사용 예
// runAtFixedRate(16ms, update, []{ return g_running; });
패턴 5: 로그에 마이크로초까지 포함 (C++20)
// C++20: std::format + chrono
#include <chrono>
#include <format>
#include <iostream>
#if __cplusplus >= 202002L
void logWithMicroseconds() {
auto now = std::chrono::system_clock::now();
auto us = std::chrono::duration_cast<std::chrono::microseconds>(
now.time_since_epoch()) % 1000000;
auto tt = std::chrono::system_clock::to_time_t(now);
std::tm* tm = std::localtime(&tt);
std::cout << std::format("{:%Y-%m-%d %H:%M:%S}.{:06d}\n",
*tm, us.count());
}
#endif
chrono 코드 점검 항목
구현 전 확인 사항:
- 벤치마크/타임아웃에는
steady_clock사용 - 로그 타임스탬프에는
system_clock사용 -
duration_cast시 잘림(truncation) 고려 -
chrono_literals로30s,100ms등 가독성 향상 - 함수 인자에
std::chrono::duration타입 명시 - 다른 clock의 time_point 혼용 금지
-
count()사용 시 단위 명시
어떤 clock을 고를지 한 줄로 정하기
경과 시간·타임아웃·벤치마크처럼 “두 시점 사이의 간격”이 필요하면 steady_clock, 로그 타임스탬프나 사용자에게 보여 줄 날짜처럼 “달력상의 시각”이 필요하면 system_clock을 씁니다. high_resolution_clock은 구현에 따라 둘 중 하나의 별칭인 경우가 많아서 따로 고를 이유가 거의 없고, 이식성이 필요한 코드에서는 오히려 의도를 흐립니다.
실제로 가장 자주 조용히 틀리는 곳은 duration_cast입니다. duration_cast<seconds>(1999ms)는 반올림 없이 1초로 잘리기 때문에, 밀리초 설정값을 초 단위 API로 넘기는 순간 타임아웃이나 재시도 간격이 짧아집니다. 사람에게 보여 줄 값이면 C++17의 round, “최소한 이만큼은 기다려야 하는” 대기 시간이면 ceil을 쓰는 습관을 들이면 이런 버그가 사라집니다.
자주 묻는 질문 (FAQ)
Q. now()를 자주 부르면 느려지나요?
A. 거의 문제되지 않습니다. Linux에서 steady_clock::now()·system_clock::now()는 보통 vDSO를 통해 커널 진입 없이 수십 나노초 안에 끝나고, Windows도 QueryPerformanceCounter 기반이라 가볍습니다. 다만 가상 머신에서 클럭 소스가 느린 방식(hpet 등)으로 잡혀 있으면 호출당 수백 나노초~마이크로초가 되기도 하므로, 초당 수백만 번 부르는 핫루프라면 한 번 재 보고 쓰세요.
Q. 여러 스레드에서 동시에 써도 되나요?
A. now() 호출과 duration·time_point 값 연산은 스레드 안전합니다(값 타입이라 공유 상태가 없음). 주의할 곳은 C 함수 쪽입니다. 로그 포맷팅에 쓰는 std::localtime·std::gmtime은 내부 정적 버퍼를 돌려줘서 스레드 안전하지 않으니 localtime_r/localtime_s를 쓰거나, C++20이면 std::format("{:%F %T}", tp)로 대체하세요.
참고 자료
- cppreference: std::chrono
- cppreference: duration
- cppreference: time_point
- C++20: Calendar and timezone
같이 보면 좋은 글
- C++ 프로파일링: perf·Tracy·VTune 도구 비교와 병목 찾는 절차
- C++ 로깅 라이브러리 (spdlog) | 빠른 로깅과 다중 싱크 [#27-3]
- C++ Boost.Asio 입문 | io_context·async_read
- C++ 시리즈 전체 보기
- C++ time_point: 시계별 시점, 해상도 변환(floor·round), 파일 시간 변환
- C++ steady_clock으로 경과 시간 재기: system_clock·high_resolution_clock과 차이