C++ 消息总线性能实测: 6 个开源方案的吞吐量与延迟对比

3 阅读8分钟

测试环境: Ubuntu 24.04, GCC 13.3, -O3 -march=native, Intel Xeon 64 vCPU

测试源码: mccc-bus/examples/competitive_benchmark.cpp


1. 为什么需要这次对比

工业嵌入式系统(激光雷达、机器人、传感器融合)中,消息总线是各模块之间解耦通信的核心组件。选型时面临三个矛盾:

  1. 吞吐量 vs 线程安全: 最快的方案(EnTT)不支持多线程,最安全的方案(ZeroMQ)延迟最高
  2. 灵活性 vs 零堆分配: std::function + shared_ptr 提供灵活回调,但每条消息 1-3 次 malloc
  3. 通用性 vs 嵌入式适配: ZeroMQ 功能强大但静态库 2 MB,MCU 上跑不起来

市面上的消息总线库各有侧重,但很少有人在同一硬件、同一测试条件下横向对比。本文选取 6 个代表性开源项目,统一测试后给出选型建议。


2. 对标项目

项目Stars类型标准Header-only
MCCC--Lock-free MPSC 消息总线C++17
eventpp~2,000事件队列/分发器C++14
EnTT~10,000ECS + 事件分发器C++17
sigslot~600信号/槽C++14
ZeroMQ~10,000IPC/网络消息C/C++
QP/C++~800Active Object 框架C++11

这 6 个项目覆盖了事件总线、信号槽、IPC 消息三大类,设计哲学各异。MCCC 是笔者开发的 Lock-free MPSC 消息总线(1,535 行,3 个头文件),作为参照基线参与对比。


3. 测试方法

3.1 统一条件

参数
消息数1,000,000 / 轮
轮次10 (取均值 +/- 标准差)
载荷24 B (struct { uint64_t seq; float x,y,z,w; })
CPU 亲和生产者 core 0, 消费者 core 1

3.2 测试模式适配

不同库设计意图不同,采用最接近其原生用法的测试方式:

测试模式说明
MCCCPublish -> ProcessBatch (双线程)生产者发布,消费者批量处理
eventppenqueue -> process (单线程)先入队再全部分发
EnTTenqueue -> update (单线程)同上
sigslot同步 emit (单线程)信号直接调用槽函数,无队列
ZeroMQpush/pull inproc (双线程)进程内 socket 传输

注意: EnTT 和 sigslot 是同步方案,不存在真正的"异步队列"。它们的高吞吐数据不能与异步方案直接对比——前者没有线程同步开销。


4. 吞吐量对比

4.1 入队吞吐量

xychart-beta
    title "入队吞吐量 (M msg/s)"
    x-axis ["EnTT", "MCCC-SPSC", "MCCC-MPSC", "sigslot", "MCCC-FULL", "eventpp-HP", "eventpp-Raw", "ZMQ"]
    y-axis "M msg/s" 0 --> 120
    bar [115.07, 33.42, 31.09, 25.92, 20.61, 6.95, 5.11, 4.48]
方案吞吐量 (M/s)延迟 (ns)同步机制线程安全
EnTT115.074无 (单线程)
MCCC SPSC BARE33.4230Wait-free store
MCCC MPSC BARE31.0932CAS 原子操作
sigslot25.9238Mutex
MCCC MPSC FULL20.6148CAS + 优先级 + 背压
eventpp HighPerf6.9530SpinLock + CAS 池
eventpp Raw5.1149std::mutex + std::list
ZeroMQ inproc4.48221Socket 内部锁

关键发现:

  • EnTT 一骑绝尘 (115 M/s),但它本质是 vector::push_back,不支持多线程。这个数字说明的是"无同步开销时 CPU 能多快"
  • sigslot 同样快 (26 M/s),因为它是同步直接调用,没有队列
  • 异步线程安全方案中,MCCC BARE_METAL 以 31 M/s 领先,是 eventpp HighPerf 的 4.5x,eventpp Raw 的 6.1x
  • MCCC 开启全部安全特性后 (FULL) 仍有 20.6 M/s,是 eventpp Raw 的 4.0x

4.2 端到端吞吐量 (生产者 + 消费者并发)

方案E2E 吞吐量 (M/s)E2E 延迟 (ns)
MCCC SPSC BARE18.8553
MCCC MPSC BARE16.4659
MCCC SPSC FULL7.04143
MCCC MPSC FULL6.82148

E2E 吞吐量受消费者处理速度约束,低于纯入队吞吐量。但这才是更贴近真实系统的指标。

4.3 不同载荷大小的影响

以 MPSC FULL 模式测试 24B/64B/128B/256B 四种载荷:

载荷MCCCeventppsigslotZeroMQ
24B6.82 M/s5.11 M/s25.92 M/s4.48 M/s
64B5.09 M/s4.61 M/s30.63 M/s2.88 M/s
128B4.37 M/s4.50 M/s30.13 M/s1.98 M/s
256B4.09 M/s3.53 M/s31.39 M/s2.14 M/s
  • sigslot 不受载荷大小影响(同步调用,不拷贝入队)
  • ZeroMQ 受影响最大(Socket 协议栈的拷贝成本随载荷线性增长)
  • MCCCeventpp 在大载荷时差距缩小,因为内存拷贝成为共同瓶颈

5. 延迟分析

方案P50 (ns)P95 (ns)P99 (ns)P99/P50
EnTT4551.25x
MCCC SPSC BARE3031311.03x
MCCC MPSC BARE3233331.03x
sigslot3840401.05x
MCCC MPSC FULL4851511.06x
eventpp Raw4950501.02x
ZeroMQ2212282281.03x

MCCC 的尾部延迟极其稳定: P99/P50 仅 1.03x,说明 CAS 操作不会产生长尾。对嵌入式实时系统来说,稳定的尾部延迟比极致的中位延迟更有价值。

BARE -> FULL 的功能开销 = 48 - 32 = 16 ns/消息(优先级检查 + 背压判断 + 统计计数)。


6. 队列溢出效应: 真实吞吐量比表观数据低 30%

上述 Publish-only 测试使用 1M 消息写入 128K 深度队列。队列满后,后续 Publish 快速失败返回,导致表观吞吐量偏高。通过对照实验揭示真实吞吐量:

实验BARE (M/s)FULL (M/s)说明
1M 无消费者31.0920.61含 ~872K fast-path failure,表观偏高
100K 无消费者22.9517.51100K < 128K,全部成功入队,真实吞吐量
1M 有消费者15.957.19跨核 cache coherence 成本
xychart-beta
    title "MPSC BARE_METAL 吞吐量对照 (M msg/s)"
    x-axis ["1M 无消费者", "100K 无消费者", "1M 有消费者"]
    y-axis "M msg/s" 0 --> 35
    bar [31.09, 22.95, 15.95]

分析:

  • 真实纯发布吞吐量 (100K 实验) 为 22.95 M/s,比表观值低 ~26%。这是诚实的数据——发布基准测试时不应回避这一点
  • 加入消费者后吞吐量再降 ~30%(22.95 -> 15.95 M/s),这是跨核 cache line 竞争的固有成本
  • FULL 模式下 cache coherence 成本更高(17.51 -> 7.19,-59%),因为 shared_mutex 读锁在高频场景成为瓶颈

7. ProcessBatchWith: 编译期分发带来 50% 提升

MCCC v2.0 新增 ProcessBatchWith<Visitor> 方法,绕过回调表和 shared_mutex,使用 std::visit 编译期分发:

消费路径MPSC BAREMPSC FULLSPSC FULL
ProcessBatch (回调表)16.46 M/s6.82 M/s7.04 M/s
ProcessBatchWith (Visitor)15.28 M/s10.20 M/s10.59 M/s
变化-7%+50%+50%

FULL 模式下 +50% 的提升来自绕过 shared_mutex 读锁和回调表间接调用。BARE 模式本身无 shared_mutex 开销,Visitor 的分支匹配反而略有损耗。


8. 特性矩阵

性能只是选型的一个维度。对嵌入式系统来说,以下特性同样关键:

特性MCCCeventppEnTTsigslotZeroMQQP/C++
异步队列YYY--YY
类型安全分发YYYY----
优先级准入Y--------Y
背压控制Y------Y--
Lock-freeY部分----部分--
零堆分配 (热路径)Y----Y--Y
线程安全YY--YYY
Header-onlyYYYY----
MISRA 合规Y--------Y
嵌入式可裁剪Y--------Y
IPC/网络传输--------Y--

资源消耗

项目核心代码行二进制大小 (-O3 -s)最小 RAM
MCCC1,53514.6 KB~4 KB
EnTT signal1,43318.3 KB不可控
eventpp1,48726.3 KB不可控
sigslot1,84834.3 KB~100 B
QP/C++5,640~50 KB~2 KB
ZeroMQ~100K+2.15 MB~1 MB

内存安全

项目队列结构热路径堆分配溢出保护
MCCC预分配 Ring Buffer优先级丢弃
eventppstd::list / CAS 池每消息一次 (Raw)无限增长
EnTTstd::vector扩容时无限增长
sigslot无队列N/A
ZeroMQ内部队列每消息一次HWM 丢弃
QP/C++事件池零 (池模式)池耗尽拒绝

9. 架构对比

flowchart TB
    subgraph MCCC["MCCC: Lock-free MPSC"]
        direction LR
        P1["Producer"] -->|CAS| RB["Ring Buffer\n128K slots\nEnvelope 内嵌"]
        RB -->|"BatchProcess"| CB["Callback\n固定数组"]
    end

    subgraph EPP["eventpp: Mutex + List"]
        direction LR
        P2["Producer"] -->|mutex| LIST["std::list\n动态分配"]
        LIST -->|"process"| CB2["Callback\nmap 查找"]
    end

    subgraph ZMQ["ZeroMQ: Socket"]
        direction LR
        P3["Producer"] -->|socket| PROTO["协议栈\n序列化"]
        PROTO -->|"recv"| CB3["Handler"]
    end

核心差异在同步机制:

项目入队同步消费同步
MCCCCAS 原子操作 (无锁)序列号检查 (relaxed)
eventppSpinLock/Mutexshared_mutex 读写分离
EnTT无 (单线程)无 (单线程)
sigslotMutex (emit 时)无 (同步)
ZeroMQSocket 内部锁Socket 内部锁

10. 选型建议

场景推荐方案原因
安全关键嵌入式 (汽车/航空)MCCC, QP/C++优先级保护 + MISRA 合规 + 零堆分配
高吞吐单线程事件EnTT极致单线程性能,无同步开销
简单解耦 (观察者模式)sigslot最简 API,同步直接调用
通用事件队列eventpp功能丰富,策略可配置
跨进程/网络通信ZeroMQ多种传输协议,生态成熟
Active Object 模式QP/C++, MCCC状态机 + 事件驱动

综合对比

维度MCCC vs eventppMCCC vs ZeroMQMCCC vs EnTT
入队吞吐量MCCC 快 4.5xMCCC 快 6.9xEnTT 快 3.7x (单线程)
E2E 吞吐量MCCC 快 2.4xMCCC 快 3.7xN/A (EnTT 无异步)
线程安全均支持均支持EnTT 不支持
优先级控制MCCC 独有均无EnTT 无
零堆分配MCCC 是ZeroMQ 否EnTT 否
二进制大小MCCC 小 1.8xMCCC 小 155xMCCC 小 1.3x

11. 结论

没有"最好"的消息总线,只有最适合场景的选择:

  • 如果你的系统不需要多线程,EnTT 和 sigslot 提供最高的单线程吞吐量,没必要引入异步队列的复杂度
  • 如果你需要异步、线程安全、且对延迟敏感,MCCC 在同类方案中性能领先(BARE_METAL 31 M/s,是 eventpp 的 4.5x),且提供优先级准入和背压控制
  • 如果你的系统需要跨进程/网络通信,ZeroMQ 是唯一选择,但要接受 2 MB 的体积和 ~220 ns 的延迟
  • 如果你在做安全关键系统 (MISRA 合规、零堆分配、确定性内存),选择范围缩小到 MCCC 和 QP/C++

本文所有数据均为同一硬件、同一编译器、同一测试条件下的实测结果,测试源码开源可复现。


参考: MCCC 源码, eventpp, newosp (MCCC 的架构设计后来融入了 newosp 基础设施库)