ROS2 性能优化完整指南
基于 ROS2 Jazzy 版本
目录
- 进程内通信 (Intra-Process)
- 零拷贝/借用消息 (Zero-Copy / Loaned Messages)
- 执行器优化
- [QoS 优化](#QoS 优化)
- 实时/实时安全特性
- 内存优化 (分配器/内存池)
- 序列化优化
- [DDS 厂商特定优化](#DDS 厂商特定优化)
- 发现机制优化
- 其他优化技巧
1. 进程内通信 (Intra-Process)
原理: 同一进程内的发布者和订阅者之间通过共享内存传递消息,避免序列化和反序列化开销。
启用方式
cpp
复制代码
// 在 NodeOptions 中启用
rclcpp::NodeOptions options;
options.use_intra_process_comms(true);
auto node = std::make_shared<rclcpp::Node>("my_node", options);
配置选项
| 设置位置 |
参数 |
可选值 |
说明 |
| NodeOptions |
use_intra_process_comms |
true/false |
节点级别全局开关 |
| PublisherOptions |
use_intra_process_comm |
Enable/Disable/NodeDefault |
发布者级别控制 |
| SubscriptionOptions |
use_intra_process_comm |
Enable/Disable/NodeDefault |
订阅者级别控制 |
| PublisherOptions |
intra_process_buffer_type |
SharedPtr/UniquePtr |
进程内缓冲区所有权类型 |
| SubscriptionOptions |
intra_process_buffer_type |
SharedPtr/UniquePtr/CallbackDefault |
订阅缓冲区类型 |
性能收益
- 避免序列化/反序列化: 消息不经过 DDS 层
- 减少内存拷贝:
IntraProcessManager 根据订阅者的所有权需求执行最少次数的拷贝
- 降低延迟: 同进程通信延迟可降低 50-90%
注意事项
- 仅适用于同一进程内的节点
UniquePtr 模式可实现真正的零拷贝(消息所有权转移)
SharedPtr 模式需要一次引用计数操作
2. 零拷贝/借用消息 (Zero-Copy / Loaned Messages)
原理: 直接从中间件借用预分配的内存缓冲区,发布和订阅时避免内存拷贝。
使用方式
cpp
复制代码
// 发布端:借用消息
auto loaned_msg = publisher->borrow_loaned_message();
loaned_msg.get().data = 42; // 直接填充数据
publisher->publish(std::move(loaned_msg)); // 发布,无需拷贝
// 订阅端:借用接收
std::unique_ptr<rclcpp::LoanedMessage<std_msgs::msg::Int32>> loaned_msg;
subscription->take_loaned_message(loaned_msg);
// 使用 loaned_msg.get().data
subscription->return_loaned_message(std::move(loaned_msg));
配置选项
| 参数 |
类型 |
默认值 |
说明 |
rmw_publisher_options.can_loan_messages |
bool |
取决于 RMW 实现 |
是否允许借用消息 |
rmw_subscription_options.can_loan_messages |
bool |
取决于 RMW 实现 |
是否允许借用接收 |
disable_loaned_message |
bool |
false |
显式禁用借用消息 |
支持的 RMW 实现
| RMW 实现 |
支持情况 |
rmw_fastrtps_cpp |
✅ 支持 |
rmw_cyclonedds_cpp |
✅ 支持 |
rmw_connextdds |
✅ 支持 |
性能收益
- 零拷贝发布: 消息直接从中间件内存发布
- 零拷贝订阅: 消息直接在中间件内存中读取
- 减少内存分配: 避免每次消息传递的堆分配
3. 执行器优化
3.1 StaticSingleThreadedExecutor
原理: 静态版本,不在每次迭代时重建可执行实体列表。
cpp
复制代码
rclcpp::executors::StaticSingleThreadedExecutor executor;
executor.add_node(node);
executor.spin();
| 特性 |
说明 |
| 静态实体集合 |
在 spin() 前创建实体,仅在添加/删除时修改 |
| 减少动态分配 |
旋转周期内无动态内存分配 |
| 适用场景 |
节点和实体数量固定的场景 |
3.2 EventsExecutor(实验性)
原理: 事件驱动架构,实体通过回调函数将事件推入队列。
cpp
复制代码
#include <rclcpp/experimental/executors/events_executor/events_executor.hpp>
rclcpp::experimental::executors::EventsExecutor executor;
executor.add_node(node);
executor.spin();
| 特性 |
说明 |
| 事件队列 |
支持自定义 EventsQueue 实现 |
| 减少维护操作 |
不遍历所有实体检查就绪状态 |
| 可定制性 |
可实现低 CPU 使用率、有界内存、确定性等目标 |
| 定时器独立线程 |
execute_timers_separate_thread 可选 |
3.3 MultiThreadedExecutor 调优
cpp
复制代码
rclcpp::executors::MultiThreadedExecutor executor(
rclcpp::ExecutorOptions{},
4, // number_of_threads: 0=自动检测CPU核心数
false, // yield_before_execute: 执行前是否让出CPU
-1 // timeout: -1=无限等待
);
| 参数 |
建议 |
number_of_threads |
设置为 CPU 核心数或回调密集型任务的需求数 |
yield_before_execute |
高优先级实时场景设为 true,一般场景设为 false |
3.4 实时执行器 (RttExecutor)
cpp
复制代码
// pendulum_control demo 中的实时执行器
pendulum_control::RttExecutor executor;
executor.add_node(node);
rttest_spin([](void *) { executor.spin_some(); }, nullptr);
配合 rttest 库进行实时性能测量和延迟统计。
4. QoS 优化
4.1 选择合适的预定义 QoS
| QoS 配置 |
History |
Depth |
Reliability |
Durability |
适用场景 |
SensorDataQoS |
KeepLast |
5 |
BestEffort |
Volatile |
高频传感器数据 |
ParametersQoS |
KeepLast |
1000 |
Reliable |
Volatile |
参数服务 |
ServicesQoS |
KeepLast |
10 |
Reliable |
Volatile |
服务调用 |
ClockQoS |
KeepLast |
1 |
BestEffort |
Volatile |
时钟同步 |
RosoutQoS |
KeepLast |
1000 |
Reliable |
TransientLocal |
日志输出 |
4.2 Best Available QoS
动态选择最佳 QoS,基于已发现的端点自动协商:
cpp
复制代码
rclcpp::QoS qos(rclcpp::QoSInitialization::from_rmw(rmw_qos_profile_best_available));
| 策略 |
值 |
说明 |
Reliability |
BestAvailable |
自动选择 Reliable 或 BestEffort |
Durability |
BestAvailable |
自动选择 TransientLocal 或 Volatile |
Liveliness |
BestAvailable |
自动选择最佳活跃性策略 |
4.3 关键 QoS 参数调优
| 参数 |
优化建议 |
depth |
高频低延迟场景设为小值(1-5),大数据吞吐场景设为大值 |
reliability |
实时场景使用 BestEffort 避免重传延迟 |
durability |
不需要历史数据时使用 Volatile 减少内存 |
deadline |
设置合理的截止期限以检测消息丢失 |
lifespan |
过期消息自动丢弃,避免处理陈旧数据 |
5. 实时/实时安全特性
5.1 内存锁定与预分配
cpp
复制代码
#include <rttest/rttest.h>
// 锁定内存到 RAM,防止页面错误
rttest_lock_and_prefault_dynamic();
// 设置实时调度优先级
rttest_set_sched_priority(90);
5.2 TLSF 分配器(确定性内存分配)
cpp
复制代码
#include <tlsf_cpp/tlsf.hpp>
// 使用 TLSF 分配器
using TLSFAllocator = rclcpp::allocator::AllocatorWithMemoryStrategy<
std::allocator<uint8_t>,
rclcpp::memory_strategies::allocator_memory_strategy::AllocatorMemoryStrategy<
std::allocator<uint8_t>>>;
| 特性 |
说明 |
| TLSF (Two-Level Segregated Fit) |
确定性时间复杂度的内存分配算法 |
| 有界分配时间 |
最坏情况分配时间可预测 |
| 低碎片化 |
适合长时间运行的实时系统 |
5.3 实时最佳实践
- 避免在实时路径中使用
new/delete
- 使用内存池预分配消息
- 锁定内存防止页面错误
- 使用实时调度策略(SCHED_FIFO/SCHED_RR)
- 避免动态加载库
6. 内存优化 (分配器/内存池)
6.1 MessagePoolMemoryStrategy
原理: 编译时预分配固定大小的消息池,使用循环数组实现 O(1) 借还。
cpp
复制代码
#include <rclcpp/strategies/message_pool_memory_strategy.hpp>
using MyPoolStrategy = rclcpp::message_pool_memory_strategy::MessagePoolMemoryStrategy<
std_msgs::msg::Float64, // 消息类型
50 // 池大小
>;
auto subscription = node->create_subscription<std_msgs::msg::Float64>(
"topic", 10, callback,
rclcpp::SubscriptionOptions(),
std::make_shared<MyPoolStrategy>());
| 特性 |
说明 |
| 静态内存分配 |
编译时确定内存需求 |
| O(1) 借还操作 |
循环数组实现 |
| 线程安全 |
互斥锁保护 |
| 消除动态分配 |
消息处理期间无堆分配 |
6.2 自定义分配器
cpp
复制代码
// 使用自定义 STL 兼容分配器
rclcpp::PublisherOptionsWithAllocator<MyCustomAllocator> options;
options.allocator = std::make_shared<MyCustomAllocator>();
auto publisher = node->create_publisher<std_msgs::msg::String>(
"topic", 10, options);
6.3 PMR 分配器支持
cpp
复制代码
// Polymorphic Memory Resources
#include <memory_resource>
std::pmr::monotonic_buffer_resource pool{large_buffer};
// 使用 PMR 分配器减少频繁的小内存分配
7. 序列化优化
7.1 序列化消息 API
cpp
复制代码
// 发布预序列化消息
rclcpp::SerializedMessage serialized_msg;
// ... 填充序列化数据 ...
publisher->publish(serialized_msg); // 无需重新序列化
// 直接接收序列化数据
rclcpp::SerializedMessage msg;
subscription->take(serialized_msg, msg); // 无需反序列化
7.2 CDR 序列化优化
| RMW 实现 |
优化特性 |
| FastCDR (Fast DDS) |
高性能 CDR 序列化 |
| Cyclone DDS |
dds_stream_check_optimize() - 可优化为 memcpy 的固定类型检测 |
| 通用 |
XCDR1/XCDR2 编码版本支持 |
7.3 使用场景
- 消息转发/桥接: 直接转发序列化数据,避免反序列化再序列化
- 录包/回放: 直接存储序列化数据
- 自定义传输: 通过非 DDS 通道传输序列化数据
8. DDS 厂商特定优化
8.1 Fast DDS
| 环境变量/配置 |
说明 |
推荐值 |
RMW_FASTRTPS_PUBLICATION_MODE |
发布模式 |
SYNCHRONOUS(低延迟)/ ASYNCHRONOUS(非阻塞) |
RMW_FASTRTPS_USE_QOS_FROM_XML |
从 XML 加载 QoS |
设为 1 启用完整 QoS 控制 |
| 共享内存传输 |
同主机通信优化 |
默认启用 |
| Data Sharing Delivery |
主机内数据共享机制 |
适用于高频大数据 |
XML QoS 配置示例:
xml
复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<profiles xmlns="http://www.eprosima.com/XMLSchemas/fastRTPS_Profiles">
<participant profile_name="my_participant">
<rtps>
<useBuiltinTransports>true</useBuiltinTransports>
</rtps>
</participant>
</profiles>
8.2 Cyclone DDS
| 配置项 |
说明 |
推荐值 |
| Iceoryx 共享内存 |
通过 Eclipse Iceoryx 实现零拷贝共享内存 |
启用 |
TCP_NODELAY |
Socket 延迟优化 |
启用 |
ENABLE_LTO |
链接时优化 |
编译时启用 |
| SPDP 禁用 |
禁用简单参与者发现 |
固定拓扑场景 |
| 多播回环 |
节点内性能优化 |
按需配置 |
Cyclone DDS XML 配置示例:
xml
复制代码
<?xml version="1.0" encoding="UTF-8" ?>
<CycloneDDS xmlns="https://cdds.io/config">
<Domain Id="any">
<General>
<Interfaces>
<NetworkInterface autodetermine="true"/>
</Interfaces>
</General>
<Tracing>
<Verbosity>config</Verbosity>
</Tracing>
</Domain>
</CycloneDDS>
9. 发现机制优化
9.1 ROS_DOMAIN_ID
bash
复制代码
# 网络隔离,减少发现流量
export ROS_DOMAIN_ID=42
| 场景 |
建议 |
| 多机器人系统 |
每个机器人使用不同的 DOMAIN_ID |
| 测试环境隔离 |
使用 domain coordinator 自动分配 |
| 生产环境 |
固定 DOMAIN_ID 避免冲突 |
9.2 Fast DDS Discovery Server
适用于大规模部署,减少发现流量:
bash
复制代码
# 启动 Discovery Server
fast-discovery-server -c config.xml
# 客户端连接到 Discovery Server
export RMW_IMPLEMENTATION=rmw_fastrtps_cpp
export FASTRTPS_DEFAULT_PROFILES_FILE=discovery_client.xml
9.3 减少发现开销
| 方法 |
说明 |
| 禁用 SPDP |
固定拓扑场景可禁用简单参与者发现 |
| 静态发现配置 |
预配置已知端点,减少动态发现 |
| 减少参与者数量 |
合并节点,减少 DDS 参与者 |
10. 其他优化技巧
10.1 回调组优化
cpp
复制代码
// 互斥回调组:组内回调不会并发执行
auto mutex_group = node->create_callback_group(
rclcpp::CallbackGroupType::MutuallyExclusive);
// 可重入回调组:组内回调可以并发执行
auto reentrant_group = node->create_callback_group(
rclcpp::CallbackGroupType::Reentrant);
| 场景 |
推荐 |
| 共享资源保护 |
MutuallyExclusive |
| 独立高频回调 |
Reentrant |
| 多线程执行器 |
合理分配回调组以提高并行度 |
10.2 隐藏节点/主题
cpp
复制代码
// 使用隐藏节点减少命名空间污染
auto node = std::make_shared<rclcpp::Node>("_my_hidden_node");
// 隐藏主题
auto pub = node->create_publisher<std_msgs::msg::String>(
"_internal_topic", 10);
10.3 参数服务优化
cpp
复制代码
rclcpp::NodeOptions options;
options.start_parameter_services(false); // 禁用参数服务
options.start_parameter_event_publisher(false); // 禁用参数事件发布
options.allow_undeclared_parameters(true); // 允许未声明参数(减少声明开销)
10.4 日志优化
cpp
复制代码
rclcpp::NodeOptions options;
options.enable_rosout(false); // 禁用 rosout 日志(减少发布开销)
10.5 选择合适的 RMW 实现
bash
复制代码
# 切换 RMW 实现
export RMW_IMPLEMENTATION=rmw_fastrtps_cpp # Fast DDS(默认)
export RMW_IMPLEMENTATION=rmw_cyclonedds_cpp # Cyclone DDS
export RMW_IMPLEMENTATION=rmw_connextdds # Connext DDS
| RMW 实现 |
特点 |
| Fast DDS |
功能丰富,共享内存支持好,适合大多数场景 |
| Cyclone DDS |
轻量级,延迟低,Iceoryx 零拷贝集成 |
| Connext DDS |
企业级,安全性好,适合认证场景 |
10.6 编译优化
bash
复制代码
# 发布模式编译
colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release
# 启用 LTO(链接时优化)
colcon build --cmake-args -DCMAKE_BUILD_TYPE=Release -DENABLE_LTO=ON
# 优化标志
colcon build --cmake-args -DCMAKE_CXX_FLAGS="-O3 -march=native"
10.7 性能测试工具
| 工具 |
用途 |
ros2 topic hz |
测量主题发布频率 |
ros2 topic bw |
测量主题带宽 |
ros2 topic delay |
测量消息延迟 |
ddsperf (Cyclone DDS) |
DDS 性能测试 |
| Fast DDS latency/throughput test |
Fast DDS 性能测试 |
performance_test_fixture |
ROS2 性能测试框架 |
性能优化检查清单
| 优化项 |
优先级 |
预期收益 |
| 启用进程内通信 |
⭐⭐⭐ |
延迟降低 50-90% |
| 使用零拷贝借用消息 |
⭐⭐⭐ |
消除消息拷贝 |
| 选择合适的执行器 |
⭐⭐⭐ |
CPU 使用率降低 30-50% |
| 优化 QoS 配置 |
⭐⭐ |
延迟降低 20-40% |
| 使用内存池 |
⭐⭐ |
消除动态分配,提高确定性 |
| 启用共享内存传输 |
⭐⭐ |
同主机通信延迟降低 |
| 使用实时分配器 |
⭐⭐ |
最坏情况延迟可预测 |
| 优化发现机制 |
⭐ |
大规模系统启动时间缩短 |
| 编译优化 |
⭐ |
整体性能提升 10-20% |
| 禁用不需要的服务 |
⭐ |
减少资源占用 |