2.2 仿真时钟与同步机制


2.2 仿真时钟与同步机制

本节摘要:Gazebo 世界里同时存在三种时间——墙钟时间、仿真时间、步长计数,实时因子 RTF 是前两者之比。所有系统(物理、传感器、你的控制器)都必须以 /clock 广播的仿真时间为唯一时间源;混用墙钟时间是仿真代码最常见的隐性 bug 来源。本节给出 RTF 的计算口径、时间源对照表和一套 RTF 掉速排障流程。

一个 0.5 秒引发的错位

ROS 2 的 RViz 默认用本机时钟给数据盖时间戳,而仿真里的传感器消息盖的是仿真时钟。当 RTF 不是 1.0 时,两套时间线的偏差持续拉大——tf 树报"extrapolation into the future"、message_filters 把正常数据当过期丢弃,全是这笔账没对齐的症状。解法只有一个:

# 让你的 ROS 节点使用仿真时钟:桥接 /clock 并声明 use_sim_time ros2 run ros_gz_bridge parameter_bridge /clock@rosgraph_msgs/msg/Clock[gz.msgs.Clock export GZ_SIM_REAL_TIME_FACTOR=1.0 # 需要严格实时时锁死 RTF # ROS 侧所有节点参数 use_sim_time:=true

为什么必须这么麻烦?因为仿真时间可以被暂停、加速、倒回,而墙钟时间不能。仿真里按了暂停键,物理和传感器全停,你的控制器如果看墙钟,会以为世界还在流逝、然后基于"过期的世界"继续做决策。仿真世界里,墙钟时间是伪钞。

三种时间与一个因子

时间 定义 由谁推进 用在哪
仿真时间 sim time 世界累计步数 × 步长 物理系统每步 +dt 所有系统的时间戳、你的控制器
墙钟时间 real time 进程真实经历的时间 操作系统 只用于计算 RTF,不该进业务逻辑
步长 step size 一步的 dt(如 1ms) 世界配置 物理精度与成本的旋钮

RTF(Real Time Factor)= 仿真时间增量 ÷ 墙钟增量。RTF=1 即实时;RTF=0.5 意味着 1 小时测试要 2 小时墙钟;RTF=4 则是"快进"。gz-sim 的 GUI 状态栏实时显示它。

RTF 掉速的三个典型来源

  1. 物理步长与场景复杂度:10ms 步长、几百个碰撞体、kp 设得极大导致求解器迭代暴涨——CPU 侧还不起账。
  2. 渲染拖累:高分辨率相机 + ogre2 + 没开传感器按需渲染,GPU 成为瓶颈。无头模式(-s)直接消掉这项。
  3. 锁步与阻塞等待:某些插件在迭代回调里做同步 IO(写盘、等网络),把整条流水线卡住。

排障顺序按"先隔离、后归因"走:

同步机制:谁等谁

多系统协作的顺序问题由服务器的迭代调度管:一次迭代 = 各系统按配置顺序执行(通常物理最后写位姿)→ 仿真时间 += dt → 发布 /clock。两个实用推论:

  • 传感器更新率独立于物理步长。物理 1ms、雷达 10Hz 完全正常——传感器系统攒够 100ms 的仿真时间才发布一帧。你在 SDF 里写 <update_rate>10</update_rate>,账记在仿真时间上,与墙钟无关。
  • 外部命令是异步到达的。你的 /cmd_vel 发到总线上,下一次迭代才被 UserCommands 系统消费。真实世界里控制延迟是纳秒到毫秒级,仿真里这个"最多一迭代"的延迟通常更小——一笔天然存在的虚实账差,做延迟补偿研究时要显式补上。

对账清单:时间相关的五分钟体检

gz topic -e -t /clock --num 10 | grep sim # 看仿真时间是否单调推进 # GUI 状态栏看 RTF;或服务端日志的 real time factor 行

逐项核对:所有 ROS 节点 use_sim_time:=true?传感器 <update_rate> 与控制器周期是否匹配(控制器 50Hz 而雷达 5Hz,融合算法会饿)?RTF<1 时你的记录脚本有没有把"仿真时长"误当"真实时长"统计?步长是否小于系统最快动态周期的十分之一(否则积分误差吃掉精度,第 3 章展开)?

把 RTF 记成一条曲线:掉速不再靠肉眼

GUI 状态栏的 RTF 是瞬时值,掉速问题的完整证据是一条随仿真时间变化的 RTF 曲线——很多瓶颈只在场景复杂度上来之后才现形(比如机器人抓起第 30 个物体那一刻)。用一行 gz stats 加简单统计就能生成这份证据:

# 每秒采样一次 RTF 与仿真时间,落盘备查 gz stats -p | stdbuf -oL grep real_time_factor >> rtf_log.csv
# 事后分析:找出 RTF 掉到 0.8 以下的时段及其仿真时刻 import re, statistics rows = [] for line in open("rtf_log.csv"): m = re.search(r"real_time_factor: ([0-9.]+).*sim_time: ([0-9.]+)", line) if m: rows.append((float(m.group(2)), float(m.group(1)))) drops = [(t, r) for t, r in rows if r < 0.8] print(f"样本 {len(rows)} 个,RTF 中位数 {statistics.median(r for _, r in rows):.2f}") if drops: t0 = drops[0][0] print(f"首次掉速发生在仿真时刻 {t0:.1f}s —— 对照当时的场景事件定位诱因")

这段分析的价值在最后一行输出:首次掉速的仿真时刻是定位诱因的锚点——把它与测试脚本的动作日志对齐,往往正好对应"生成第 N 个物体""相机进入高纹理区域""插件开始写盘"中的某一项,比笼统的"仿真有点卡"能省掉一半排查时间。配合 2.1 节的进程隔离实验(同一时刻用 -s 复跑),渲染账与物理账当场分账。

本节要点回顾

  • 三种时间一个因子:仿真时间是唯一合法时间源,墙钟只配用来算 RTF。
  • use_sim_time 不是可选项:桥接 /clock + 全节点启用,是仿真-ROS 协作的地基。
  • 传感器挂仿真时间、命令异步到达:更新率与步长解耦;一迭代的命令延迟是天然账差。
  • RTF 掉速三查:先无头隔离渲染,再减载隔离物理,最后查插件阻塞。

时间这条暗线铺好了,剩下的一个架构问题是:如何在不改 Gazebo 源码的前提下,把自定义行为注入这条流水线?答案是插件——2.3 的主角。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U