Linux编程:从 Apollo CyberRT 中提取可独立使用的Time 模块

0. 引言

C++编程中直接使用 std::this_thread::sleep_for 来控制循环频率存在以下问题:

  • 不精确的睡眠时间sleep_for 仅保证至少睡眠指定的时间,实际睡眠时间可能更长。
  • 忽略任务执行时间:没有考虑任务本身执行所消耗的时间,导致实际循环频率低于预期。
  • 累积误差:由于每次循环的时间不一致,可能导致频率逐渐偏离目标值。

而Apollo Cyber中的Time模块能提供稳定的频率控制,它主要用于Apollo系统的时间管理和控制。本文从Apollo Cyber中抽离出可以独立运行的Cyber Time模块代码,并编写了CMake和Bazel脚本,更多阅读:简单实例演示CMake和Bazel的使用

完整代码见:apollo-cyber-time

1. Apollo Cyber Time模块如何确保循环频率稳定的?

ApolloC yber Time模块中有一个Rate,该类通过计算每次循环应该消耗的总时间(周期时间),然后在每次循环中测量实际消耗的时间,最后通过睡眠剩余的时间来维持循环的频率。

简单解释:

  • 初始化:创建 Rate 对象时,设定目标频率,例如 50 Hz。计算出每个循环的周期时间,即 cycle_time = 1 / frequency
  • 循环开始:记录当前时间作为循环的开始时间。
  • 执行任务:运行你的任务代码,这部分会消耗一定的时间。
  • 计算实际耗时:任务执行完后,再次记录当前时间,与开始时间相减,得到任务执行的实际耗时。
  • 计算需要睡眠的时间:用周期时间减去任务的实际耗时,得到需要睡眠的时间 sleep_time = cycle_time - actual_duration
  • 睡眠:如果 sleep_time 大于零,线程就睡眠这段时间;如果小于等于零,说明任务执行时间已经超过了周期时间,直接进入下一次循环。
  • 进入下一循环:重复上述步骤。

流程图如下:

开始循环
记录开始时间
执行任务
记录结束时间
计算任务耗时 actual_duration = 结束时间 - 开始时间
计算睡眠时间 sleep_time = cycle_time - actual_duration
sleep_time > 0?
睡眠 sleep_time
不睡眠, 继续
进入下一次循环
  • 为什么这样能保证频率稳定?

    • 自动补偿:通过计算任务实际耗时,Rate 类能够自动补偿任务执行时间的变化,调整睡眠时间,以保持循环的总时间接近设定的周期时间。

    • 防止累积误差:每次循环都基于当前的开始时间和设定的周期时间计算,不依赖于系统时间,防止了因任务执行时间波动导致的时间漂移。

    • 灵活应对:如果任务执行时间偶尔超过了周期时间,Rate 类会在下一次循环中自动调整,而不会导致整体频率的严重偏离。

2. 使用实例

// cyber_rate_example.cc
#include <iostream>

#include "cyber_rate.h"

using apollo::cyber::Rate;
using apollo::cyber::Time;

int64_t g_int = 0;

// 模拟耗时任务
void run_cpu() {
  for (int i = 0; i < 20 * 1024 * 1024; i++) {
    g_int += i * i;
  }
}

int main(int argc, char* argv[]) {
  Rate r(50.0);  // 50 Hz

  while (true) {
    std::cout << apollo::cyber::Time::Now() << "  " << r.CycleTime() << std::endl;
    run_cpu();
    r.Sleep();
  }
  return 0;
}

执行结果:

$ ./cyber_rate_example                                                                                                                                               
2024-12-04 16:20:18.762900129  0.000000000s
Detect forward jumps in time2024-12-04 16:20:18.807047345  0.044137374s
Detect forward jumps in time2024-12-04 16:20:18.844054587  0.037013302s
Detect forward jumps in time2024-12-04 16:20:18.882810484  0.055771244s
Detect forward jumps in time2024-12-04 16:20:18.920043783  0.037231840s
Detect forward jumps in time2024-12-04 16:20:18.956554207  0.053744286s
Detect forward jumps in time2024-12-04 16:20:18.995153732  0.038598737s
Detect forward jumps in time2024-12-04 16:20:19.032947124  0.056392537s
Detect forward jumps in time2024-12-04 16:20:19.071329286  0.038381747s
Detect forward jumps in time2024-12-04 16:20:19.109067791  0.056120240s
Detect forward jumps in time2024-12-04 16:20:19.147067100  0.037999439s
Detect forward jumps in time2024-12-04 16:20:19.186053445  0.056985717s
Detect forward jumps in time2024-12-04 16:20:19.223447473  0.037395190s

3. 代码分析

Rate模块接口如下:

class Rate {
 public:
  explicit Rate(double frequency);
  explicit Rate(uint64_t nanoseconds);
  explicit Rate(const Duration&);
  void Sleep();
  void Reset();
  Duration CycleTime() const;
  Duration ExpectedCycleTime() const { return expected_cycle_time_; }

 private:
  Time start_;
  Duration expected_cycle_time_;
  Duration actual_cycle_time_;
};

完整代码结构如下:

$ tree . -L 1                                                                                                                                                               .
├── BUILD
├── CMakeLists.txt
├── cyber_duration.cc
├── cyber_duration.h
├── cyber_duration_test.cc
├── cyber_rate.cc
├── cyber_rate_example.cc
├── cyber_rate.h
├── cyber_time.cc
├── cyber_time.h
├── cyber_time_test.cc
└── WORKSPACE

类关系图

uses
1
1
uses
1
1
uses
1
1
uses
1
1
«class»
Time
+int64_t current_time_ns
+Time()
+Time(uint64_t nanoseconds)
+Time(double seconds)
+Duration operator-(const Time& rhs)
+Time operator+(const Duration& rhs)
+Time operator-(const Duration& rhs)
+Time& operator+=(const Duration& rhs)
+Time& operator-=(const Duration& rhs)
+static Time Now()
+std::string ToString()
+double ToSecond()
+double ToMillisecond()
«class»
Duration
+int64_t duration_ns
+Duration()
+Duration(uint64_t nanoseconds)
+Duration(double seconds)
+Time operator+(const Duration& rhs)
+Time operator-(const Duration& rhs)
+Duration& operator+=(const Duration& rhs)
+Duration& operator-=(const Duration& rhs)
+double ToSecond()
+double ToMillisecond()
«class»
Rate
+double frequency_hz
+Duration cycle_duration
+Rate(double frequency)
+Rate(uint64_t nanoseconds)
+Rate(int nanoseconds)
+void Sleep()
+double CycleTime()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

橘色的喵

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值