Appearance
为什么同一台机器里,共享内存比网络快?—— 进程间通信的"直达"与"绕行"
阅读时间: 2026-08-13
共享内存是最快的进程间通信方式,这句话在延迟对比里有多"快"?本机回环(loopback)TCP 同样不离开物理机器,两者的差距却可达 2~3 个数量级。本篇从数据路径逐段拆解:共享内存是"映射一次、之后直读物理页"的直达,本机 TCP 是"即使不出机器也要走完整协议栈"的绕行——差的不只是速度,而是每一跳都在付出代价。
相关:shared-memory.md(两套共享内存接口)、mmap.md(mmap 机制)、socket-kernel-internals.md(socket 内核路径)、context-switch.md(切换开销)、syscall.md(系统调用开销)。
零、要回答的问题
两个进程跑在同一台机器上,交换数据有两种典型做法:
- 共享内存:
mmap(MAP_SHARED)把同一块物理页映射进两个进程的地址空间,然后直接读写; - 本机 TCP:进程 A
send(),进程 Brecv(),走127.0.0.1回环。
疑问很自然:TCP 走的是环回接口,数据不出物理机器、不碰网卡,为什么还是比共享内存慢一两个数量级?共享内存到底"快"在哪,网络到底"贵"在哪?
核心论点:两者的差距不是"速度"而是"路径长度"。共享内存是数据路径上的"直达"——建好映射后,读写就是普通 load/store,CPU 直接访问物理页;本机 TCP 是"绕行"——即便数据不出机器,也完整经过 syscall、协议栈、拷贝、调度唤醒每一跳,而每一跳都要付出数量级不等的代价。
一、两条路径全貌
图一:共享内存 —— 一次映射,之后直达物理页

图析:
mmap只是把虚拟地址 → 物理页的映射关系写进页表(一次性成本,含第一次访问的缺页),之后 A、B 对该地址的读写直接由 CPU 访存完成。数据从头到尾只有一份物理副本,谁写谁见,没有"搬运"这个概念。跨核时的一致性由硬件 cache 协议(MESI 系)保证,CPU 只付出 cache miss 的代价(几十个周期),不付出任何软件代价。
图二:本机回环 TCP —— 不出机器,也走完整协议栈

图析:A 的
send()是一次系统调用(copy_from_user拷进内核sk_buff),随后数据在内核里走完 TCP/IP 协议处理,经 loopback 设备"回环"进 B 的接收队列,最后内核唤醒阻塞在recv()的 B,再一次系统调用copy_to_user拷给用户。B 侧通常先被切走睡眠、再被唤醒——这就是至少 2 次上下文切换的来源。注意全程没有网卡、没有 DMA,但协议栈、队列、唤醒一样都不少。
二、逐段拆解:共享内存为什么"零成本"
共享内存的全程开销分为一次性和稳态两部分:
| 阶段 | 开销 | 说明 |
|---|---|---|
| 建映射(一次性) | 1 次 mmap syscall + 首次访问缺页 | shm_open/shmget 等创建接口另计;此后永不再付 |
| 稳态读写 | 纯 load/store | L1 命中 ~4 周期(~1.3ns@3GHz),L3 命中 ~40 周期,DRAM ~100+ 周期 |
| 跨核一致性 | 硬件 cache 协议兜底 | MESI 系自动处理,软件零参与 |
| 同步(可选) | 原子操作 / futex | 仅当双方需要"通知"时才有,见 memory-model |
关键认知:建好映射后,共享内存的数据面完全脱离内核——没有系统调用、没有拷贝、没有调度。它把"通信"降级成了"访存",把"协议"降级成了"缓存一致性硬件"。
三、逐段拆解:本机 TCP 的每一跳贵在哪
以"A 发一段数据、B 收"的单次往返为例,固定成本项:
| 成本项 | 量级(有据) | 出处 |
|---|---|---|
① send() 系统调用(直接开销) | 数百 ns / ~1000+ 周期 | syscall.md |
| ② syscall 的间接代价(KPTI 页表切换、流水线冲刷、cache/TLB 污染余震) | 每次 syscall 额外数十~数百周期,余震难以切分 | syscall.md |
③ 拷贝 1:用户→内核(copy_from_user) | 与数据量成正比 | socket-kernel-internals.md |
| ④ 内核内协议处理(TCP 状态机、校验和、loopback 回环、缓冲队列管理) | 数百 ns ~ μs 级 | — |
| ⑤ 唤醒接收方(等待队列 + 调度) | 上下文切换 ~1-5μs,隐性恢复 5-50μs | context-switch.md |
⑥ recv() 系统调用 + 拷贝 2:内核→用户 | 同①③ | — |
注意⑤是隐藏大头:B 进程多半睡在 recv() 上,A 发数据后内核唤醒 B,B 被调度进来——一次跨进程切换的直接开销 ~3-5μs,加上 cache/TLB 恢复期可达 5-50μs(context-switch.md §5.3)。这在共享内存路径上完全不存在(除非一方主动 sleep 等通知)。
四、量化对比表
| 维度 | 共享内存 | 本机回环 TCP |
|---|---|---|
| 稳态单次数据路径 | load/store 直访物理页 | send→协议栈→loopback→recv |
| 每往返的 syscall 数 | 0 | ≥2(send + recv) |
| 每往返的上下文切换 | 0(除非跨核/主动 sleep) | ≥2(发送方陷入 + 接收方被唤醒) |
| 用户↔内核数据拷贝 | 0 | ≥2 次(且协议层可能再拷贝) |
| 内核中转 | 无(映射后完全用户态) | 每字节都过一遍协议栈 |
| 一致性/同步 | 硬件 cache 协议 + 自选同步原语 | TCP 内置(流控/拥塞/缓冲) |
| 稳态单次延迟量级 | 几十 ns(L3 命中)~ 数百 ns(DRAM) | 10~100 μs 量级(视负载与缓冲配置) |
| 量级差距 | — | 约 2~3 个数量级 |
注:延迟为量级参考,随硬件/内核版本/负载浮动;差距的量级结论(而非具体数字)是本篇的论据。
五、深层原因:为什么差这么多
5.1 通信模型不同:共享状态 vs 消息传递
- 共享内存是"共享状态"模型:数据只有一份,访问它就像访问自己的变量。没有"投递"动作,所以没有投递成本;
- TCP 是"消息传递"模型:数据必须被搬运(拷出、拷进),每搬一次付一次费,还要维护"搬运过程"本身的状态(发送缓冲、接收队列、确认、重传)。
5.2 每一跳的成本结构不同
共享内存把"跨进程"这个难题甩给了硬件(cache 一致性协议),网络则全部用软件扛(协议栈 + 调度 + 拷贝)。硬件的并行性远超软件:cache 一致性在 L3 命中的代价是 ~40 周期(约 13ns),而一次 syscall 就要数百 ns,一次调度唤醒要 1~5μs——软件路径上最便宜的一跳,也比硬件兜底贵一两个数量级。
5.3 优化空间的差异
- 网络的每一跳都能被"部分优化"(zero-copy 省拷贝、io_uring 合并 syscall、RSS/绑核减切换),但无法消除——协议栈和调度是消息传递的内置成本;
- 共享内存的路径"短无可短":读写已经是 CPU 指令集的最短路径,唯一能做的优化是保持 cache 命中(数据对齐、绑核、避免伪共享)。
六、公平性:共享内存的隐藏成本与边界
"共享内存更快"不等于"处处更优",差距要打几个折扣:
| 场景 | 共享内存的真实代价 |
|---|---|
| 需要通知对方"我写完了" | 必须另配同步(futex/信号量/事件fd),同步本身是 syscall + 切换,把一部分优势还回去了 |
| 高频互相覆盖同一行 cache | 伪共享(false sharing),跨核 ping-pong 反而比消息传递更慢 |
| 跨 NUMA 节点 | 远端内存访问比本地慢 2~3 倍,共享内存的"低成本"需要绑核/绑定 NUMA 策略支撑 |
| 数据有结构化生命周期 | 没有天然的"消息边界",流式/定长/指针序列化都要自己管 |
网络侧的反面优势:TCP 开箱即得流控、缓冲、对端感知(断连检测);io_uring 等异步接口能把 syscall 成本摊薄;跨机部署时网络是唯一选择。共享内存快的前提是"同机 + 你来负责同步与生命周期"。
七、结论:回答开头的三个问题
- 为什么共享内存比本机 TCP 快 2~3 个数量级? 因为快慢不取决于数据是否离开机器,而取决于数据路径上经过多少跳软件环节——共享内存把"通信"降级为"访存"(0 syscall、0 拷贝、0 内核中转),本机 TCP 则完整经过 syscall、协议栈、拷贝、调度唤醒每一跳,每跳成本都在数百 ns ~ 数 μs 级。
- 快在哪? 快在稳态路径:映射是一次性投资,之后的数据面完全脱离内核,由 CPU + cache 一致性硬件直接完成。
- 什么时候该用哪个? 同机高频小数据、延迟敏感、双方是"共谋"的协作进程 → 共享内存;需要消息边界/流控/跨机/低开发成本 → TCP。
一句话总结:共享内存 vs 本机 TCP 的本质是"把跨进程通信交给硬件 cache 协议(直达,几十 ns)" vs "交给软件协议栈(绕行,10~100 μs)"——差的不是机器内还是机器外,而是路径上有没有 syscall、拷贝和调度。