1、时间戳的基石:理解操作系统时钟源
做量化交易系统,时间戳就是命根子。我见过太多团队,策略逻辑写得漂漂亮亮,结果一到实盘就出问题——最后查出来,是时间戳的精度不够,或者时钟源选错了。
今天咱们就聊聊操作系统里两个最核心的时钟源:CLOCK_REALTIME 和 CLOCK_MONOTONIC。说白了,这就是你给每一笔交易打上时间标签的「表」。表不准,后面的一切都白搭。
1.1 两个时钟源,到底差在哪?
先看一张图,把这两个时钟源的关系理清楚。
这张图其实已经把核心差异说清楚了。我简单展开一下:
- CLOCK_REALTIME:就是墙上挂的那个钟。你拿手机看时间,就是这个。它对应的是真实世界的时间,可以被 NTP 调整,也可以被管理员手动改。
- CLOCK_MONOTONIC:从系统启动开始,一直往上加,绝不回头。它不受任何外部干扰,只跟 CPU 的晶振有关。
核心原则:在量化交易系统中,计算延迟、测量性能、生成订单序列号,一律使用 CLOCK_MONOTONIC。只有需要展示给人看的时间,才用 CLOCK_REALTIME。
1.2 为什么不能用 CLOCK_REALTIME 算延迟?
我直接说一个真实案例吧。曾经有个团队,用 CLOCK_REALTIME 来测量订单从生成到成交的延迟。结果某天凌晨 2 点,NTP 同步把系统时间往回调整了 0.5 秒。你猜怎么着?
所有延迟数据全部变成负数了。系统以为订单在成交之后才生成。那天的回测数据全废了。
为什么会这样?因为 CLOCK_REALTIME 可以被外部修改。NTP 同步、管理员手动调时间、甚至闰秒调整,都会让这个时钟发生跳跃。你想想看,如果时间突然往回跳了 1 秒,那这 1 秒内的所有交易,时间戳都是乱的。
避坑指南:我曾经在一个高频交易项目中,发现某个模块的延迟数据偶尔出现异常大的负值。排查了三天,最后发现是某台机器的 NTP 配置有问题,导致时钟频繁回跳。从那以后,我要求所有延迟测量代码必须使用 CLOCK_MONOTONIC,并且在启动时检查时钟源是否正确。
1.3 微秒级精度,到底够不够?
现在很多交易系统号称「纳秒级精度」。但说实话,在软件层面,纳秒级精度基本是个伪命题。为什么?
因为操作系统本身就有调度延迟。你调用 clock_gettime() 这个系统调用,从用户态切换到内核态,再切换回来,这个过程本身就要几百纳秒到几微秒。你测出来的时间,其实已经包含了这个开销。
我个人习惯,在软件层面追求微秒级精度就足够了。真正的纳秒级精度,需要硬件时间戳(比如网卡上的 PTP 硬件时间戳),那是另一回事。
来看一段实际代码,感受一下微秒级时间戳怎么拿:
#include <time.h>
#include <stdio.h>
// 获取微秒级时间戳(单调时钟)
uint64_t get_monotonic_us() {
struct timespec ts;
// 使用 CLOCK_MONOTONIC_RAW 避免 NTP 调整影响
clock_gettime(CLOCK_MONOTONIC_RAW, &ts);
return (uint64_t)ts.tv_sec * 1000000 + (uint64_t)ts.tv_nsec / 1000;
}
// 获取微秒级实时时间戳
uint64_t get_realtime_us() {
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
return (uint64_t)ts.tv_sec * 1000000 + (uint64_t)ts.tv_nsec / 1000;
}
int main() {
uint64_t t1 = get_monotonic_us();
// 模拟一些计算
for (int i = 0; i < 1000000; i++) __asm__ volatile("nop");
uint64_t t2 = get_monotonic_us();
printf("耗时: %lu 微秒\n", t2 - t1);
return 0;
}
小技巧:注意我用了 CLOCK_MONOTONIC_RAW 而不是 CLOCK_MONOTONIC。前者完全不受 NTP 影响,连频率调整都没有。虽然精度一样,但更「纯净」。在 Linux 上,这是做延迟测量的最佳选择。
1.4 不同时钟源的精度对比
我整理了一张表,把常见的时钟源和它们的特性列出来,方便你对照选择:
| 时钟源 | 精度 | 是否单调 | 受NTP影响 | 适用场景 |
|---|---|---|---|---|
| CLOCK_REALTIME | 纳秒级(实际微秒) | 否 | 是 | 日志、展示、数据库时间 |
| CLOCK_MONOTONIC | 纳秒级(实际微秒) | 是 | 频率可能微调 | 延迟测量、性能分析 |
| CLOCK_MONOTONIC_RAW | 纳秒级(实际微秒) | 是 | 完全不受影响 | 高精度延迟测量(推荐) |
| CLOCK_BOOTTIME | 纳秒级(实际微秒) | 是 | 频率可能微调 | 包含休眠时间的测量 |
| CLOCK_TAI | 纳秒级(实际微秒) | 否 | 是 | 国际原子时场景 |
1.5 实战中的选择策略
说了这么多理论,咱们落地到实战。在量化交易系统里,我一般这样用:
- 订单时间戳:用
CLOCK_MONOTONIC_RAW生成内部序列号,同时记录一个CLOCK_REALTIME用于展示。 - 延迟测量:全部用
CLOCK_MONOTONIC_RAW,确保差值绝对准确。 - 日志记录:用
CLOCK_REALTIME,方便人阅读和排查问题。 - 跨机器时间同步:用 PTP(精确时间协议),配合硬件时间戳,精度可以到亚微秒级。
记住一个原则:任何需要「算差值」的地方,都用单调时钟。任何需要「给人看」的地方,都用实时时钟。这两个角色不要混用。
嗯,关于时钟源的基础知识,就先聊到这里。你可能会问:那在实际代码里,怎么保证每次获取时间戳的开销最小?怎么避免系统调用带来的延迟?这些内容,咱们后面会详细展开。
公众号:蓝海资料掘金营,微信deep3321