std::chrono::steady_clock 是唯一靠谱的选择,因其单调递增且不受系统时钟调整影响;system_clock 会跳变,high_resolution_clock 在某些平台只是其别名。

为什么 std::chrono::steady_clock 是唯一靠谱的选择
因为只有它能保证单调递增且不受系统时钟调整影响,std::chrono::system_clock 在 NTP 同步或手动改时间时会跳变,日志时间戳就可能倒流;std::chrono::high_resolution_clock 在某些平台(如旧版 MSVC)只是 system_clock 的别名,不保证高精度或单调性。
实操建议:
- 始终用
std::chrono::steady_clock::now()获取时间点,再转成毫秒整数:auto now = std::chrono::steady_clock::now(); auto ms = std::chrono::duration_cast<:chrono::milliseconds>( now.time_since_epoch()).count();</:chrono::milliseconds> - 避免每条日志都调用
now()——高频写入时开销明显。可考虑在日志缓冲区入口统一打一次时间戳,或用线程局部缓存(但需注意多线程下首次调用仍要取一次) - 不要用
gettimeofday()或clock_gettime(CLOCK_MONOTONIC, ...)封装——跨平台成本高,且 C++11 已提供等效、更安全的抽象
如何避免日志格式化成为性能瓶颈
字符串拼接和 strftime 类函数在每条日志中执行,是毫秒级日志最常被忽视的拖慢点。尤其当使用 std::ostringstream 或 fmt::format 时,动态内存分配和字符转换开销远超时间戳获取本身。
实操建议:
- 预分配固定长度缓冲区(如 64 字节),用
snprintf直接写入:char buf[64]; snprintf(buf, sizeof(buf), "%lld.%03d", ms / 1000, (int)(ms % 1000));
- 毫秒部分直接用
ms % 1000计算,比除法 + 取余 + 转字符串快得多;整秒部分用ms / 1000,避免浮点运算 - 若需完整日期时间(如 “2024-05-22 14:36:01.123”),优先用
std::chrono::system_clock::to_time_t+localtime_r+snprintf,而非std::put_time(后者构造 locale 开销大,且非 trivial)
异步写入时如何保证毫秒精度不被掩盖
异步日志本质是把 I/O 延后到后台线程,但时间戳必须在“日志语句执行那一刻”捕获,否则拿到的是后台线程取任务的时间,误差可达几毫秒甚至更多。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
实操建议:
- 时间戳必须在主线程/业务线程中生成,并随日志内容一起入队,例如结构体中存
int64_t timestamp_ms - 后台线程只负责格式化和写文件,不再调用任何时间函数
- 队列类型选无锁环形缓冲区(如
boost::lockfree::spsc_queue或自实现),避免入队时锁竞争导致时间戳采集延迟抖动 - 注意:如果后台线程格式化耗时超过 1ms(比如加了冗余的堆栈打印或 JSON 序列化),那“毫秒级”只剩名义意义——此时应砍掉非必要字段,或升级为微秒级采样+聚合输出
Windows 下 QueryPerformanceCounter 还值得手动封装吗
不值得。现代 MSVC(≥2015)和 Clang/LLVM on Windows 中,std::chrono::steady_clock 底层就是 QueryPerformanceCounter,且已处理好频率换算、溢出校正等细节。自己封装反而容易踩精度丢失(如用 double 存 tick 数)、跨平台断裂、以及未处理休眠唤醒导致的计数器暂停问题。
唯一例外是极老环境(VC2010 或更早),但那种场景通常也不追求毫秒级日志精度。
真正该关注的是:确保编译器没把 steady_clock::now() 优化掉(加 volatile 或内存屏障没必要,标准库实现已保证可观测性),以及链接时启用高精度定时器支持(Windows 默认开启,无需额外操作)。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










