在普通应用里,调用一次当前时间几乎不值得讨论。但在高吞吐 tracing 系统里,每个 span 都要记录开始和结束时间,单次几十纳秒的开销很快就会变成真实成本。文章作者面对的预算是每个 span 大约 50 纳秒,结果发现朴素写法光时间戳就吃掉了几乎全部预算。
OpenTelemetry 风格的 span 通常需要两个时间:开始的 wall clock 时间,以及用于计算持续时间的 monotonic clock。原因很简单:系统时间可能被 NTP 调整、管理员修改或闰秒影响,而 monotonic clock 更适合算持续时间。常见实现会在开始时取一次系统时钟和一次单调时钟,结束时再取一次单调时钟。
理论上 Linux 上 `clock_gettime()` 并不慢。现代 glibc 会通过 vDSO 在用户态读取时钟,避免真正陷入内核。可即便如此,三次时间读取叠在一起仍然可能达到 46 到 49 纳秒。对大多数程序无所谓,对超高频 instrumentation 就很刺眼。
文章的核心是拆开 Linux 时间读取的底层机制。x86 上有 TSC,也就是 timestamp counter。较新的处理器支持 invariant TSC,它会以稳定频率递增,不随 CPU 省电状态改变。内核通过 vDSO 把必要的换算参数映射给用户态,让程序可以用 TSC 和校准数据推导当前时间。
优化空间就藏在这里。如果你理解了系统时钟和单调时钟之间的关系,就不一定要重复做完整读取。可以减少不必要的时钟查询,复用已知偏移,或者直接走更轻量的路径。作者最终把初始方案的计时开销砍掉了超过一半。
这类优化最容易被误解成“过早优化”。但它其实是系统设计里的预算意识:当一个操作会发生在每个请求、每个 span、每个事件上,它就不再是小细节。可观测性系统尤其如此,因为它本来是辅助工具,一旦自身开销过高,就会改变被观测系统。
当然,自己读 TSC 或绕过标准 API 也有风险。不同架构、虚拟化环境、内核版本、CPU 频率特性都会影响假设。文章也提醒,新内核版本会调整 vDSO 数据页布局。性能优化越靠近硬件,越需要清楚适用边界。
这篇文章的价值不只是“怎样更快取时间”,而是展示了工程优化的正确姿势:先量化预算,再测量瓶颈,然后理解抽象层下面的真实机制。很多看起来免费的 API,在纳秒级系统里都会露出成本。