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
大于零,线程就睡眠这段时间;如果小于等于零,说明任务执行时间已经超过了周期时间,直接进入下一次循环。 - 进入下一循环:重复上述步骤。
流程图如下:
-
为什么这样能保证频率稳定?
-
自动补偿:通过计算任务实际耗时,
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
类关系图: