返回专辑
·Johan·4 分钟阅读

chrono:单调时钟与系统时钟别混用

steady_clock 与 system_clock 的分工,传感器时间域对齐,以及超时别用错钟。

chrono:单调时钟与系统时钟别混用

1. 传感器延迟突然 -200ms

把 system_clock 当传感器时间戳,NTP 步变那一秒 IMU 与 lidar 对不齐;把 steady_clock 当日志 wall time,运维按「几点几分」搜日志又对不上。日志混用两种 clock,排查会浪费一整晚。时钟选错不是 off-by-one——是整条数据流时间轴错位,回放 bag 时 message 乱序是典型后果,sim 里更明显。传感器延迟突然负两百毫秒,往往是混钟而非传感器坏了。多相机外触发对齐若混 wall clock 回退,偶发乱序会像「无线干扰」一样难复现——应在 code review 里禁止用 system 做 duration。

2. 三种 clock 分工

clock用途陷阱
steady_clock测延迟、超时、帧间隔不能当 wall time
system_clock日志、与 NTP 对齐可能跳变
high_resolution_clock可能是上面任一 alias移植前先查

测延迟/性能用 steady_clock;消息对齐用 header stamp 或 rclcpp::Time(ROS time),别用 system_clock::now() 去减 msg header stamp。硬件时间戳若来自 GPS/PTP,用 system 表示 wall time 可以,但要处理闰秒与同步失败回退。high_resolution_clock 在部分 libstdc++ 上是 system 别名——移植新板子时先 static_assert 或查实现,别假设「高精度等于单调」。

3. 超时与 duration 类型

cpp
const auto t0 = std::chrono::steady_clock::now();
processLidar(msg);
const auto ms = std::chrono::duration<double, std::milli>(
  std::chrono::steady_clock::now() - t0).count();
if (ms > 33.0) { /* over budget */ }

chrono milliseconds(10) 与裸纳秒数在模板里不等价——API 暴露 milliseconds period,避免 call site 单位传错(控制环十毫秒写成十微秒见过不止一次)。sleep_until(steady+50ms) 适合固定周期;别用 sleep(1) 测 1Hz 循环,NTP 步进会让周期漂移。对外暴露超时参数时,类型用 chrono duration 而非 int 毫秒,可让编译期与文档一致,减少 magic number。

4. 与 ROS 2 时间

rclcpp::Time 区分 ROS time 与 system time;仿真 use_sim_time 时 /clock 驱动 now()。C++ 侧 bag 对齐用 ROS time,profiler 用 steady。rclcpp::Rate 基于 ROS clock;multi-camera 外触发对齐用 message stamp 做 time_point 比较,别 wall clock 回退。watchdog 用 steady_clock、message header 用 ROS time——混在一个 log line 里必须两个字段都打印,否则 sim 排障对不上。lifecycle 节点在 inactive 与 active 切换时,确认 timer 仍绑定正确的 clock 源,别在 sim 下仍用 wall sleep。multi-session 复盘若缺 header stamp,很难与 bag 对齐——时间证据链与参数快照同等重要。

5. 日志与运维

日志输出 human time 用 system_clock 转 format,但 duration 差永远用 steady。跨时区 audit 用 UTC,控制环别碰 system_clock。multi-session 复盘时,若日志只有 wall time 没有 header stamp,很难与 bag 对齐——证据链不完整,分不清是算法还是时间轴问题。utc_clock 仅审计用,别进控制环。录包复盘应同时保留关键消息的 header stamp 与节点侧 steady 段耗时,事故袋才能还原「当时对齐用哪个钟」。

6. 仿真与 rate

rclcpp::Rate 用 ROS clock;仿真节点用 sim time 驱动 rate,别用 wall sleep。超时(等 transform)用 node steady timer;消息对齐用 message header stamp——两类用途别混在一个变量里。比较不同 clock 的 time_point 应编译报错——若 SFINAE 绕过去,单元测试要覆盖。use_sim_time 切换后,所有 watchdog 与 rate 应重新验证——常见遗漏是某处仍用 system_clock 做 duration,sim 下表现为周期漂移或误触发。

7. 案例:NTP 步进让 watchdog 误触发

某节点用 system_clock 做帧超时,机房 NTP 步进后日志里「decode 耗时负两百毫秒」,watchdog 误报。改成 steady_clock 测延迟、header stamp 对齐消息后消失。根因不是 decode 变快,是混钟。教训:测延迟永远 steady;对齐消息永远 ROS time 或 header stamp。运维改 NTP 策略前,应通知算法团队哪些节点仍用 system 做 duration——这类依赖应逐步清零。

8. 验收

  • static_assert steady_clock::is_steady。
  • mock steady 前进 100ms,watchdog 触发。
  • 回放 bag message 不乱序。
  • 启动摘要打印 timeout 用的 clock 类型。
  • 日志行若含延迟,同时打 steady 耗时与 header stamp。

steady 测延迟,ROS time 对齐消息,system 给人看——三类用途别混,混了就是排查夜。时间契约写进 API 与日志字段,比口头约定更能扛 NTP 与 sim 切换;启动摘要应打印 watchdog 与 rate 绑定的 clock 类型;code review 禁止用 system 做 duration 差分,sim 与 field 切换时尤易踩坑;比较不同 clock 的 time_point 应编译报错,单测覆盖 SFINAE 绕过的路径。

← 全部文章

johan's blog