std::chrono::steady_clock是首选,因其单调递增、不受ntp校时或系统休眠影响,避免负延迟和抖动;system_clock可能跳变,clock()多线程未定义且精度低。

为什么 std::chrono::steady_clock 是首选
因为它的单调性与系统休眠无关,不会受 NTP 调整或时钟回拨影响,适合做精确耗时测量。用 std::chrono::system_clock 可能导致负值或跳变,clock() 在多线程下行为未定义,且精度通常较差。
实操建议:
- 始终用
std::chrono::steady_clock::now()获取时间点,而非构造函数隐式调用 - 避免跨线程共享同一个计时器实例——不同线程的
now()调用本身是安全的,但共享对象可能引发竞态(比如你加了start()/stop()状态管理) - 如果需要纳秒级精度,确认平台支持:
steady_clock::period::den为 10⁹ 表示纳秒,但实际分辨率取决于硬件和 OS,Linux 下通常为 ~15–25ns,Windows 高精度模式可达 ~100ns
如何避免拷贝/移动语义导致的时间丢失
一个常见错误是把计时器当作普通对象随意传值,结果 start() 在原对象上调用,elapsed() 却在副本上调用,而副本没启动过。
实操建议:
- 禁用拷贝构造与赋值:
Timer(const Timer&) = delete;和Timer& operator=(const Timer&) = delete; - 如需转移所有权,显式支持移动:
Timer(Timer&&) noexcept并把m_start移为空值(如std::chrono::time_point<:chrono::steady_clock>{}</:chrono::steady_clock>),否则移动后原对象调用elapsed()可能返回极大值 - 更稳妥的做法是只允许栈上构造 + 引用传递,或用
std::unique_ptr<timer></timer>管理生命周期
elapsed() 返回什么单位最实用
返回 double 毫秒最平衡:整数微秒易溢出(10⁹ μs = 1s,累计 4294 秒就溢出 uint32_t),而直接返回 nanoseconds.count() 是 long long,不方便 printf 或日志对齐。
实操建议:
- 提供模板化接口,例如
template<typename unit="std::milli"> double elapsed() const</typename>,内部用std::chrono::duration_cast<:chrono::duration unit>>(...)</:chrono::duration> - 不要默认返回秒——太小,三位小数后全是 0;也不要硬编码毫秒——有些场景(如 GPU kernel 计时)需要微秒级分辨率
- 若用于性能敏感路径,避免每次调用都做
duration_cast,可缓存std::chrono::duration<double std::milli></double>类型的差值
Windows 下高精度计时要额外注意什么
Windows 默认 QueryPerformanceCounter 分辨率依赖硬件,但某些虚拟机或旧 BIOS 可能返回不稳定的频率,导致 steady_clock::now() 出现抖动甚至倒流(极少见,但真实存在)。
实操建议:
- 不自行封装
QueryPerformanceCounter——std::chrono::steady_clock已处理大部分兼容逻辑 - 如果发现计时结果异常(如
elapsed()返回负值),检查是否在调用前未调用start(),或是否被编译器优化掉空循环(加volatile或asm volatile("" ::: "memory")防止误优化) - Release 模式下确认未启用 /LTCG 或 /GL 导致链接时优化干扰计时点顺序
真正难的不是写一个能跑的计时器,而是让 start() 和 stop() 的边界足够紧、且不被编译器或 CPU 乱序执行干扰。哪怕只差几个周期,在高频 benchmark 中也会放大成显著偏差。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











