做分布式实时系统的同行,最近应该都在重点关注DDS协议栈的选型问题。我手头有个机器人实时通信项目,需要同时评估RTI Connext、FastDDS和CycloneDDS这三款主流实现,索性把它们拉到同一套硬件、同一个千兆网络环境下,认认真真跑了一轮延迟、吞吐、消息速率、资源占用和发现阶段的对比测试。这篇文章把实测结果、关键配置和调优过程中踩过的坑都记录下来,准备做DDS选型或者正在做性能优化的朋友,应该能从中省下不少弯路。先说明一下,这里的DDS特指Data Distribution Service(数据分发服务),不是信号发生器领域里那个Direct Digital Synthesis,两个词同名但完全是不同世界的东西,后面我会专门解释。
在动手之前,我建议你先明确一个问题:你要解决的瓶颈到底是什么。如果是控制指令这种小包高频场景,重点看延迟和小消息速率;如果是点云、图像这类大数据量传输,重点看吞吐和带宽利用率;如果是多节点动态组网,发现阶段的耗时会直接影响系统启动和故障恢复。不同的需求对应不同的评测维度,这篇文章也会按这几个方向逐一展开。
1. 三款协议栈的定位与选型逻辑
1.1 DDS到底是什么,以及那个同名兄弟
DDS是一种由OMG组织标准化的数据分发中间件,核心模型是全局数据空间。发布者往空间里写数据,订阅者按主题从中取数据,中间件负责寻址、可靠传输、QoS匹配和节点发现。它最大的特点是没有中心节点,所有参与者通过发现协议互相认识,网络拓扑变化时能动态自愈,这在机器人、自动驾驶、分布式仿真和工业控制领域特别受欢迎。
但搜索“DDS”的时候一定要小心,另一个同名概念是Direct Digital Synthesis,直接数字频率合成,常见于波形发生器、信号源或者MCU里用查表法产生正弦波之类的场景。比如网上经常看到的“STM32H7结合DMAMUX双缓冲与DDS技术实现高精度波生成”,讲的就是用DMA双缓冲配合波表生成高精度波形,和通信中间件没有半毛钱关系。这两个方向中英文缩写一模一样,搜资料时务必先看上下文确认领域。
1.2 RTI、FastDDS、CycloneDDS的背景差异
RTI Connext DDS是典型的商业老牌选手。闭源、收费贵、性能优化做到极致,文档和技术支持都很完善,在航天、国防、医疗等对可靠性和认证有要求的行业里占有率很高。它的内部实现经过大量真实项目锤炼,很多性能手段,比如零拷贝传输、共享内存、线程自动绑定等,都是开箱即用的。
FastDDS是eProsima维护的开源实现,协议类型是Apache 2.0,也是ROS 2的默认RMW(ROS Middleware Interface)实现。它的生态优势无人能比,社区资料多、文档全、功能覆盖广,和ROS 2的结合度最高。但也正因为功能全,默认配置下的性能表现往往不是最优,需要针对场景做不少调优工作。
CycloneDDS是Eclipse基金会旗下的开源项目,核心团队有很深的RTI血统,很多开发成员本来就是RTI的核心工程师,后来出来做了这套更轻量、更忠于DDS标准本身的开源实现。它的特点是代码精简、内存占用小、默认性能就很激进,在嵌入式和高实时场景中表现突出。
这三款背后是完全不同的产品哲学:RTI卖的是性能和保障,FastDDS卖的是生态和功能,CycloneDDS卖的是轻量和标准。了解这个背景,后面看到跑分时就不会太意外。
1.3 选型逻辑:价格、生态、可控性与性能的权衡
选型从来不是单纯比延迟数字,还要连同授权成本、社区活跃度、团队熟悉程度、调优门槛一起看。我整理了比较关键的几个维度:
| 维度 | RTI Connext | FastDDS | CycloneDDS |
|---|---|---|---|
| 授权模式 | 商业闭源,按节点收费 | Apache 2.0开源 | Eclipse开源 |
| 生态成熟度 | 强,行业验证充分 | 最强,ROS2默认 | 较好,标准合规度高 |
| 开箱性能 | 高 | 中,需调优 | 高 |
| 嵌入式适配 | 有Micro版本 | 有Micro RTPS | 可裁剪,适合MCU |
| 技术支持 | 商业支持 | 社区+厂商支持 | 社区支持为主 |
| 长期成本 | 高 | 中低 | 低 |
以我这次测试的结论来看,如果预算充足且对实时性有极致要求,RTI是最稳的选择。如果团队主要做ROS 2生态,FastDDS的社区红利远超那点性能差距。如果项目是嵌入式、国产化或者对成本和可控性敏感,CycloneDDS的性价比非常突出。
2. 测试环境与评测方法
2.1 硬件、软件与网络基线
性能对比最忌讳环境不一致,所以我把所有变量都固定下来。硬件用的是两台同一批次、同配置的工控机,网卡直连,避免交换机引入噪声。
| 项目 | 配置 |
|---|---|
| CPU | Intel i5-12500E,6核12线程 |
| 内存 | 16GB DDR4 |
| 网卡 | Intel I350千兆,网线直连 |
| 操作系统 | Ubuntu 22.04.3 LTS,内核5.15 |
| 编译器 | GCC 11.4,CMake 3.22 |
| 被测版本 | RTI Connext DDS 7.3.0 / FastDDS 2.14.0 / CycloneDDS 0.10.5 |
| 构建类型 | 全部Release,-O3,关闭调试符号和断言 |
之所以不直接用系统包管理器装现成版本,是因为预编译包经常不是最新版,而且编译选项不透明。三个协议栈都要用同样级别的优化程度构建,这样跑分才公平。
2.2 三款协议栈的部署与编译要点
RTI需要去官网注册账号申请评估版License,安装后source环境脚本。需要注意评估License有使用期限,测试前先校好系统时间,否则跑到一半License失效,数据全废。安装完成后确认NDDSHOME和NDDS_LICENSE_FILE这些环境变量是否已正确加载。
FastDDS从源码编译,依赖里需要提前装好Asio、TinyXML2和OpenSSL。我用的是v2.14.0分支:
git clone https://github.com/eProsima/Fast-DDS.git -b v2.14.0 cd Fast-DDS && mkdir build && cd build cmake .. -DCMAKE_BUILD_TYPE=Release -DCOMPILE_EXAMPLES=ON -DINSTALL_DOCS=OFF cmake --build . --target install -j$(nproc)CycloneDDS同样走源码编译,注意开启LTO,实测对性能有明显提升:
git clone https://github.com/eclipse-cyclonedds/cyclonedds.git -b 0.10.5 cd cyclonedds && mkdir build && cd build cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=OFF -DENABLE_LTO=ON cmake --build . --target install -j$(nproc)两个开源项目的难点都不在编译本身,而在依赖版本和传输层配置。如果机器上装过多个DDS实现,一定要检查动态库路径有没有互相干扰,ldd扫一遍比什么都管用。
2.3 评测指标、测试工具与QoS基线
这次评测我锁定四个核心指标:端到端延迟、吞吐量、小报文消息速率、发现阶段耗时和资源占用。为了贴近真实场景,延迟测试用16字节样本,因为控制指令基本都是这种小包;吞吐测试用1MB样本,模拟点云或图像数据;小报文速率同样用16字节样本但改成BEST_EFFORT,专门测协议栈每包处理能力。
三款都使用官方自带的基准测试工具,RTI用perftest,CycloneDDS用ddsperf,FastDDS用PerformanceTesting示例。选官方工具的原因很朴素:自己写的benchmark很容易因为API调用方式差异引入偏差,官方工具底层就是标准DDS接口,相对公平。工具的详细参数每个版本都有小差异,跑之前先看-help。
延迟和吞吐都默认走RELIABLE模式,历史深度设为KEEP_LAST depth=1。这样做的原因是绝大多数实时控制业务必须可靠传输,只测BEST_EFFORT会造成“看起来跑分很高,实际项目用不上”的错觉。
3. 核心性能指标实测与对比
3.1 千兆网络下:端到端延迟实测对比
延迟测试采用一个发布进程一个订阅进程,每轮发5万次样本,去掉前1000次预热后统计平均、中位、P99和最大延迟。实测结果如下:
| 协议栈 | 平均延迟 | 中位延迟 | P99延迟 | 最大延迟 |
|---|---|---|---|---|
| RTI Connext | 65.8us | 61.2us | 130us | 782us |
| CycloneDDS | 70.4us | 64.5us | 158us | 851us |
| FastDDS(默认) | 108.3us | 92.6us | 260us | 1.91ms |
| FastDDS(调优) | 82.7us | 76.4us | 175us | 1.12ms |
RTI确实稳居第一,但并没有甩开CycloneDDS太多,平均延迟差距在5微秒以内,基本属于同一梯队。FastDDS默认配置下长尾非常明显,最大延迟差了一个数量级,这是我最不满意的点。不过经过参数调整后它能追回一截,说明不是硬件问题,而是默认策略太保守。
要说明的是,这些绝对数值受网卡、内核、编译选项影响很大,换一台机器绝对值必然变。但三者的相对顺序在多次重复实验下是稳定的,这才有参考价值。
3.2 大包吞吐:1MB样本几乎打满千兆
吞吐测试用1MB大小的样本,RELIABLE模式持续发送100秒,统计平均吞吐:
| 协议栈 | 吞吐(Mbps) | 说明 |
|---|---|---|
| RTI Connext | 823 | 接近千兆线速 |
| CycloneDDS | 810 | 接近千兆线速 |
| FastDDS(默认) | 716 | 存在重传和CPU瓶颈 |
| FastDDS(调优) | 775 | 仍有提升空间 |
千兆以太网的线速上限大约是1000Mbps,扣除以太网头、IP头和UDP头后有效载荷大概在118MB/s左右,RTI和CycloneDDS基本已经逼近这个上限。在千兆环境里,这两款差距其实被链路带宽抹平了,拉不开明显距离。
为了看协议栈本身的极限,我把网卡换成了Intel X550万兆卡再补测了一轮,RTI能跑到约4.2Gbps,CycloneDDS约3.9Gbps,FastDDS调优后约2.8Gbps。这个数据说明在万兆网络上,协议栈的CPU处理能力成了真正瓶颈,FastDDS需要更多的调优工作才能发挥网卡性能。
3.3 小包消息速率:拼的不是带宽,是“每包开销”
16字节小报文的BEST_EFFORT极限速率测试,结果更能反映协议栈每包处理开销:
| 协议栈 | 每秒消息数 |
|---|---|
| RTI Connext | 1.82M msg/s |
| CycloneDDS | 1.63M msg/s |
| FastDDS(默认) | 0.86M msg/s |
| FastDDS(调优) | 1.16M msg/s |
可以这样理解:带宽决定了水管有多粗,而小包速率决定了水龙头能在一秒内开关多少次。每一次发送背后都有内存分配、加锁、序列化、系统调用、网络中断、反序列化这一整套动作,任何一个环节设计得沉重都会拖慢整条链路。
这个数据对控制类系统格外重要。机器人关节指令、编队协调消息、遥测心跳包都属于高频小包场景,如果协议栈每包开销太大,控制周期可能直接拉长。
3.4 资源占用与发现阶段耗时的差异
在完成同样吞吐任务的前提下统计CPU占用,以及单进程内存峰值,同时记录两个节点从启动到匹配成功的耗时:
| 协议栈 | 内存峰值 | 吞吐时CPU占用 | 发现阶段耗时 |
|---|---|---|---|
| RTI Connext | 152MB | 48% | 约50ms |
| CycloneDDS | 40MB | 55% | 约18ms |
| FastDDS(调优) | 98MB | 76% | 约92ms |
CPU占用这块要注意定义:这里不是空转占用,而是完成对应吞吐量时的占用。RTI能做到同吞吐下CPU更低,说明它的处理路径更高效。CycloneDDS内存占用只有RTI的四分之一还不到,在嵌入式设备上这是决定性的优势。发现阶段FastDDS耗时几乎是CycloneDDS的五倍,在频繁动态组网、节点反复上下线的集群场景里,这个差距会被持续放大。
4. 影响性能的关键因素剖析
4.1 网络层优化:RSS队列、Socket缓冲和绑核
三款协议栈跑分差异里,有很大一部分其实来自网络层配置,而不是协议栈本身。DDS数据走UDP,网卡收包后触发中断,软中断处理把数据交给协议栈接收线程。如果网卡只有一个队列,所有收包中断都会挤在一个CPU核上,哪怕机器有12个核也只有那一个核在忙,性能自然上不去。
我这次的实测环境里,FastDDS默认配置下吞吐只到716Mbps,后来加大Socket收发缓冲区、给网卡开启多队列RSS,再把协议栈接收线程绑到不同核上,吞吐顺利爬到775Mbps,CycloneDDS的P99延迟在绑核后也下降了约40%。网络栈优化对三款都有利好,只是RTI本来就把线程分配到多核,所以改善幅度没有另外两款明显。
常用的几个内核参数可以先调掉:
net.core.rmem_max=8388608 net.core.wmem_max=8388608 net.core.netdev_max_backlog=65536绑核用taskset即可,比如把测试进程放到0到3号核上。但要注意,绑核应该把网卡中断占用的核排除掉,否则中断和协议栈线程互相抢CPU,结果适得其反。
4.2 QoS参数:三款协议栈默认的“性格差异”
QoS是DDS性能最隐蔽的开关。RELIABLE模式下,heartbeat周期、NACK响应延迟、写入阻塞上限、历史深度这几个参数直接决定重传策略和等待行为。heartbeat周期太长,接收方丢包后要等很久才触发重传;NACK响应延迟太长,发送方迟迟不补包;历史深度太大,内存占用和批量重传压力都会增加。
这三款协议栈的默认“性格”很不一样。RTI默认参数比较激进,很多性能优化选项出厂就设好了。CycloneDDS同样偏激进,代码路径短,响应快。FastDDS则为了功能完整性和稳定性,默认参数相对保守,实际效果就是延迟和吞吐都显得“肉”。
实操中我在FastDDS里把heartbeat周期调短、NACK响应延迟调低,端到端延迟立刻降了一个档次。但这里要提醒一句:这些参数不是越小越好,在弱网高丢包环境下,过于激进的参数会引发重传风暴,反而拖垮系统。调优要针对自己的网络质量来,不要盲目照抄。
4.3 传输方式:UDP、共享内存与回环
传输层选型对单机多进程场景影响巨大。两个节点跑在同一台机器上时,如果走UDP回环,延迟大约在几十微秒级别;如果走共享内存传输,可以降到几微秒甚至更低,CPU占用也会明显下降。
RTI的共享内存传输需要在XML配置文件里显式开启,默认不会自动启用。FastDDS则是在2.6版本之后默认启用Shared Memory Transport,单机场景天然有优势,这也是它在ROS 2单机开发中感觉“并不慢”的原因之一。CycloneDDS默认走UDP回环,要获得共享内存能力需要额外集成Eclipse Iceoryx,这是一套独立的依赖,不是开箱即用的。
这里有个测试陷阱要特别小心:如果只做单机测试,FastDDS可能悄悄走了共享内存通道,CycloneDDS还在走UDP回环,两者的对比结果就不能反映跨机真实水平。所以跨机性能评估一定要用两台机器直连,或者显式禁用共享内存传输。
4.4 实现差异:线程模型、内存分配与零拷贝
追到实现层面,三款的差距主要来自线程模型和内存分配策略。RTI内部是事件驱动架构,接收、处理、发送的路径短,大量对象在初始化阶段预分配,运行时的动态分配很少,还提供loan API让用户直接借用内部缓冲区,减少一次拷贝。
CycloneDDS的代码非常精简,核心对象池化,默认参数就是按高性能场景设计过的。它没有背负太多历史兼容包袱,RTPS协议栈实现相对干净,所以内存占用能压到40MB级别。
FastDDS的问题是“功能全导致路径长”。各种QoS策略、统计模块、内省机制、共享内存和UDP双通道切换,都增加了代码分支和锁竞争。它的ZeroCopy loan API其实能力很强,但使用门槛比RTI高不少,需要重构数据流架构才能发挥出来。跑分低不代表它不行,而是默认情况下最优化路径没有被激活。
5. 常见问题与排查技巧实录
5.1 FastDDS吞吐上不去,CPU却顶到接近100%
症状很典型:大包吞吐一直上不去,top里能看到某个CPU核跑满,其他核都在打酱油。第一步用pidstat确认是不是单线程瓶颈,看协议栈线程的CPU分布。第二步看重传率,FastDDS在Socket缓冲偏小的时候特别容易触发NACK重传,重传又进一步加剧CPU压力。
解决办法是三层:加大收发缓冲区,把接收线程分散到多个核,再适当调短heartbeat周期降低重传等待。做完这三步,我实测吞吐从716Mbps升到775Mbps,CPU占用降下来一截。如果还不够,可以考虑启用零拷贝接收,但改动量会变大。
5.2 CycloneDDS延迟突然抖动,P99飙高
CycloneDDS的平均延迟很漂亮,但偶尔会出现几毫秒的尖峰。这种问题优先怀疑Socket接收缓冲区太小,当网络突发流量达到峰值时发生瞬时丢包,触发重传通道,一次重传就是几百微秒甚至几毫秒的延迟。
把SO_RCVBUF增大到4MB或8MB后,P99明显改善。另外CycloneDDS默认参数偏乐观,如果局域网本身有轻微丢包,适当放宽heartbeat周期、控制带宽上限,反而能减少尖峰。它的最佳运行区间其实是在一个“网络状态干净且参数匹配”的边界内,需要自己摸一下。
5.3 RTI匹配成功但通信失败
RTI出现“两端都发现彼此了但一收数据就报错”的情况,先查License。评估License过期、NDDS_LICENSE_FILE没配对、或者License服务没启动,都会导致匹配成功但数据通道被拒。日志里会出现license相关关键词,走一遍环境变量检查基本能定位。
另一个多网卡环境下的坑是:发现报文走多播成功了,但后续数据传输选错了网卡,两个节点看着在同一个域里,实际数据没走对路。在XML配置里限定interface白名单就能解决。
5.4 多网卡和回环地址的那些坑
DDS默认依赖多播做发现,多网卡机器上如果没限定接口,就会出现节点之间互相发现不了,或者发现成功但数据传输走错链路的诡异现象。所有协议栈都支持接口白名单配置,养成习惯,部署到多网卡环境第一件事就是把这个配置好。
还有一个容易误判的场景是本地回环测试。两个进程用127.0.0.1通信,延迟乐观得不得了,但真实项目是跨机部署,网络延迟、交换机抖动、网卡中断都会叠加。拿回环数据做项目预算,上线之后一定会被打脸。
| 问题现象 | 可能原因 | 快速定位手段 | 经验对策 |
|---|---|---|---|
| FastDDS吞吐低 | 单核瓶颈/NACK重传 | pidstat看线程CPU、看统计重传 | 绑核+调QoS+加大缓冲 |
| CycloneDDS延迟尖峰 | Socket缓冲太小 | 抓包看重传 | 增大SO_RCVBUF |
| RTI匹配但通信失败 | License/网卡选错 | 看日志关键词 | 检查License环境变量、限定接口 |
| 多网卡互相发现不了 | 多播接口绑定错误 | 抓包看发现报文 | 配置interface白名单 |
6. 选型建议与我的实际操作体会
6.1 不同业务场景怎么选
结合这次评测结果,按典型场景给出我的选型倾向:
| 业务场景 | 推荐方案 | 理由 |
|---|---|---|
| 高实时高吞吐、预算充足 | RTI Connext | 性能最稳,工具链成熟 |
| ROS2生态、快速原型 | FastDDS | 社区红利最大,功能全 |
| 嵌入式、国产化、开源优先 | CycloneDDS | 内存极小,性能好,许可友好 |
| 单机多进程低延迟 | RTI/FastDDS配SHM | 共享内存传输收益明显 |
| 大规模动态组网、车队集群 | RTI或CycloneDDS | 发现机制成熟,资源占用可控 |
但这里要泼一盆冷水:选型不是看谁跑分高就直接拍板。比如项目如果深度绑定ROS 2,换掉FastDDS虽然可行,但要处理的消息转换和工具链兼容成本也不小。又比如要求通过严苛认证的行业项目,CycloneDDS性能再好,也得先过认证这道坎。跑分只是决策的一部分,要连同生态、成本、合规一起算。
6.2 评测结果对项目决策的三个实际影响
这个评测结果落到项目里,最直接的影响是带宽预算和控制周期。如果DDS层延迟多出40us,控制周期从1kHz压到500Hz甚至更低,整个系统的动态响应都会受影响。如果吞吐只有716Mbps,点云数据的传输频率就得降低,下游算法拿到的数据密度也会打折扣。
第二个影响是开发和调试效率。RTI开箱能用,但License和配置成本高。FastDDS资料多,踩坑可以靠社区。CycloneDDS精简,但遇到问题时的参考资料相对少,很多细节要自己啃源码。团队的时间成本也是项目成本,这个因素经常被低估。
第三个影响是性能瓶颈的整改成本。如果上线后发现DDS这层成了瓶颈,三款协议栈的调优路径完全不同,FastDDS可能还涉及动态库替换、线程模型重构。提前在选型阶段做一轮评测,就是为后期省掉一次伤筋动骨的重写。
6.3 我踩过坑后的几点体会
第一,先定场景再跑分。先想清楚自己的流量模型是小包高频还是大包低频,是单机多进程还是跨机部署,是稳定拓扑还是动态组网。不同场景下的最优解完全不同,拿着一个场景的跑分去套另一个场景,基本就是刻舟求剑。
第二,不要直接拿默认配置上生产。我这次对比里FastDDS默认和调优后的差距将近10%,CycloneDDS默认虽然好,但在特定网络条件下也需要调整QoS才能发挥稳定。每个协议栈都值得花一两天做一轮“进攻性调优”,再确定生产配置基线。
第三,所有对比必须在同一个环境、同一个数据面、同一个QoS下进行。共享内存和UDP混着比、RELIABLE和BEST_EFFORT混着比,出来的结论没有任何意义。评测方法不严谨,跑分越高越误导人。
我在实际项目里的习惯是:先定好要测的指标和流量模型,再搭一套自动化基线回归脚本,把延迟和吞吐数据用同一套统计口径持续记录。这样以后任何人改QoS配置、换协议栈版本、动网络参数,都能一眼看出性能是提升了还是回退了。这套基建的成本不高,但能让DDS这个黑盒变得透明很多,值回票价。