RDMA 远程直接内存访问技术
当数据中心网络进入 100G/200G/400G 时代,一个尴尬的事实浮出水面:网卡比软件快得多。40G 网络的理论包间隔已小于一微秒,而传统 TCP 路径上一次系统调用加几趟内存拷贝就要花掉十几微秒——CPU 成了瓶颈,网络反而不是。RDMA(Remote Direct Memory Access)的答案是把三件事做到极致:零拷贝(数据不经过任何中间缓冲)、内核旁路(数据面完全不过内核协议栈)、对端零 CPU(单边操作直接读写远程内存)。本文拆解 RDMA 的架构与 verbs 编程模型,并用一段零依赖 Python 模拟把三条收益量化出来。
一、传统 TCP 路径的成本解剖
用 socket 发送一块 64KB 数据,数据与 CPU 分别经历了什么:
- 应用缓冲 → 内核套接字缓冲:send() 系统调用,一次上下文切换 + 一次内存拷贝;
- TCP/IP 协议栈处理:分段、序号、校验和、拥塞控制、定时器——全部消耗 CPU 指令;
- 内核缓冲 → 网卡:DMA 搬运;
- 接收方反向重演:网卡中断 → 软中断协议栈处理 → recv() 系统调用 → 拷贝到应用缓冲。
合计 3 次拷贝、2 次系统调用、1 次中断、大量协议栈指令。更糟的是这些成本随消息大小线性增长——带宽越大的机器,CPU 被网络吃得越多。
二、RDMA 的架构:把数据面整个搬进网卡
RDMA 网卡(RNIC,如 Mellanox/NVIDIA ConnectX 系列)自己实现了完整的有序可靠传输协议,工作方式:
- 内存注册(Memory Registration):应用预先把要收发的内存区域注册给网卡,页被钉住(pin)防止换出,网卡拿到翻译表可以直接 DMA 访问。这是一次性成本,之后这块内存的收发完全不需要 CPU;
- 队列对(Queue Pair, QP):通信端点是发送队列 + 接收队列的组合。应用把工作请求(WR)提交到队列,写一下门铃寄存器通知网卡——整个过程是几十条指令的用户态操作,无系统调用;
- 网卡硬件接管:网卡从发送队列取请求,直接从注册内存 DMA 数据、切包、加协议头、发出;对端网卡收到后直接 DMA 进对端注册内存,完成队列(CQ)里放一条完成记录。两端 CPU 在数据面全程零参与。
三、单边 vs 双边:RDMA 的标志性语义
- 双边操作(SEND/RECV):类似消息传递,发送方 SEND 需要接收方预先挂出 RECV 缓冲区,两端 CPU 都参与(虽然都不拷贝数据);
- 单边操作(READ/WRITE/ATOMIC):本端直接读或写对端的注册内存——对端 CPU 完全不参与,甚至不需要知道这次访问发生了。这是『远程直接内存访问』名称的本义,也是 RDMA 与传统消息传递最本质的区别。
单边语义改变的是系统设计的思维方式:远程内存可以像本地内存一样被寻址。分布式共享内存、远程持久化数据结构(远端 B+Tree、哈希表)、无 CPU 开销的存储副本写入,都建立在单边 READ/WRITE 之上。单边 ATOMIC(compare-and-swap、fetch-and-add)则提供了无锁的远程同步原语。
四、动手实验:三条收益的量化对比
下面代码用成本模型模拟同机房 64KB 消息在两种路径上的传输:统计数据拷贝次数、CPU 参与指令数与端到端延迟,并演示单边 READ 的对端零开销。零依赖,Python 3 直接运行:
SIZE = 64 * 1024 # 64KB 消息
CPU_CYCLE_NS = 0.33 # 3GHz 每周期
COPY_BYTES_PER_CYCLE = 32 # AVX 每周期拷贝 32B
SYSCALL_NS = 1500 # 系统调用 ~1.5us
IRQ_NS = 2000 # 中断处理 ~2us
DMA_NS = 8000 # 网卡 DMA 搬运 64KB
WIRE_NS = 5000 # 网线 ~5us
def sim_tcp():
copies, cpu_ns, instr = 0, 0.0, 0
copies += 1 # 应用 -> 内核缓冲
cpu_ns += SYSCALL_NS
instr += SIZE // COPY_BYTES_PER_CYCLE
copies += 1 # 内核 -> 网卡(协议栈处理)
instr += SIZE // 64 # TCP 分段/校验和等
cpu_ns += IRQ_NS + SYSCALL_NS # 对端中断 + recv
copies += 1 # 内核 -> 应用
instr += SIZE // COPY_BYTES_PER_CYCLE
cpu_ns += instr * CPU_CYCLE_NS
return copies, cpu_ns, instr, cpu_ns + DMA_NS + WIRE_NS
def sim_rdma():
instr = 50 # 仅提交工作请求: 写门铃
cpu_ns = instr * CPU_CYCLE_NS
return 0, cpu_ns, instr, cpu_ns + DMA_NS + WIRE_NS
在沙箱(Python 3,无第三方库)上的真实运行输出:
消息大小: 64KB, 同机房链路
== 数据拷贝次数 ==
TCP 路径 : 3 次 (应用->内核->网卡->内核->应用)
RDMA 路径: 0 次 (注册内存由网卡直接 DMA, 零拷贝)
== CPU 参与 ==
TCP 路径 : 5120 条指令, CPU 时间 6.7us
RDMA 路径: 50 条指令, CPU 时间 0.02us
CPU 开销比: 405x
== 端到端延迟模型 ==
TCP : 19.7us
RDMA: 13.0us
延迟比: 1.5x
== 单边语义演示: 远程读 ==
TCP 模型: 读对端数据 = 对端必须运行 recv/send, 消耗对端 CPU
RDMA READ: 本端网卡直接从对端注册内存 DMA, 对端 CPU 指令数 = 0
-> 对端甚至可以完全不知情(数据被读), 这是"远程直接内存访问"的本义
校验通过: RDMA 零拷贝 / CPU 开销降两个数量级 / 延迟显著更低
五、结果解读:数量级差异在哪里
- CPU 开销 405 倍是最本质的差距:TCP 的 5120 条指令里大头是数据拷贝(每 32 字节一条)与协议栈处理(分段、校验和、拥塞控制状态机);RDMA 把这些全部卸载到网卡硬件,CPU 只剩提交工作请求的 50 条指令。真实场景里一台跑满 100G TCP 的机器要把十几个核喂给网络栈,RDMA 下这些核全部还给业务。
- 拷贝次数 3 → 0:注意 RDMA 的零拷贝有一个前提——内存必须先注册(页钉住 + 地址翻译表交给网卡)。注册是慢操作,所以高性能应用都用预注册的内存池复用,而不是每次传输临时注册。
- 延迟 1.5 倍在本模型里看起来温和,因为模型简化了协议栈排队与调度抖动;真实世界里 TCP 的尾延迟(P99)受中断合并、软中断调度影响常常抖到数百微秒,而 RDMA 的 P99 延迟稳定在 10 微秒量级——尾延迟的确定性才是低延迟系统最看重的指标。
- 单边读的零对端开销:实验中 TCP 模型读远程数据要求对端 CPU 跑完整 recv/send 循环,RDMA READ 则完全绕开对端。这把『读远程数据』的成本从『两台机器的 CPU』变成『一台机器的网卡』,是分布式内存池化的技术基石。
六、承载协议:InfiniBand、RoCE 与 iWARP
- InfiniBand:RDMA 的原生载体,链路层就保证无损(credit-based 流控),性能最好但需要专用交换机和子网管理器,成本与生态封闭是门槛;
- RoCE(v1/v2):RDMA over Converged Ethernet,跑在标准以太网上。RoCEv2 封装在 UDP/IP 里可以路由,是数据中心主流;关键前提是无损以太网——需要 PFC(优先级流控)+ ECN 拥塞管理,配置复杂是主要运维负担;
- iWARP:RDMA over TCP,可容忍有损网络但实现复杂度高、延迟略逊,市场份额很小;
- Soft-RoCE:纯软件实现的 RoCE,任何网卡都能跑 RDMA verbs 语义,是学习和测试的利器(本文概念均可先在 Soft-RoCE 上验证)。
七、工程应用
- AI 训练:NCCL 在 IB/RoCE 网络上自动走 GPUDirect RDMA——GPU 显存之间直接搬运梯度,CPU 与系统内存完全不参与,这是大规模训练线性扩展比的通信基础(本站《NCCL 集合通信原理解析》会展开);
- 分布式存储:NVMe-oF(NVMe over Fabrics)用 RDMA 把远程 NVMe 盘的访问延迟压到接近本地盘;Ceph、DAOS 的高性能数据面均基于 RDMA;
- 内存数据库:FaRM(Microsoft)用单边 READ 构建分布式内存事务,单集群吞吐上亿次操作/秒,是 RDMA 系统设计的里程碑论文;
- 解耦架构:内存解耦(memory disaggregation)把内存池化成独立资源,计算节点通过 RDMA 按需挂载远程内存,是数据中心资源利用率的下一战场。
八、常见误区
- 以为买了 RDMA 网卡就有性能:没有 PFC/ECN 配置的有损以太网上,RoCE 丢包重传的代价会让性能比 TCP 还差;无损网络配置是前置工程,不是可选项。
- 每次传输临时注册内存:注册操作涉及页表钉住与 IOMMU 映射,开销以微秒计且随大小增长;正确做法是启动时建好内存池反复复用。
- 把单边 WRITE 当消息用:单边写不通知对端,对端必须自己轮询内存标志位或等完成事件——用单边语义做通知需要精心设计内存序与可见性,否则就是数据竞争的重灾区。
- 忽视 QP 数量与连接拓扑:全互联集群的 QP 数量是 N² 量级,每个 QP 占网卡缓存,大规模集群必须用共享接收队列(SRQ)、动态连接传输(DCT)或 XRC 控制连接规模。
- 用 TCP 的编程直觉写 verbs 程序:verbs 是异步完成队列模型(提交 WR → 轮询 CQ),把同步 socket 思维硬套上去会丢掉大半性能;新接触者建议从 perftest(ib_write_bw 等)与 UCX/libfabric 等高层封装起步。
九、参考文献
- Dragojević, A. et al. FaRM: Fast Remote Memory. NSDI 2014.
- Kalia, A., Kaminsky, M., Andersen, D. Design Guidelines for High Performance RDMA Systems. USENIX ATC 2016.
- Mellanox/NVIDIA. RDMA Aware Networks Programming User Manual (verbs API). docs.nvidia.com/networking.
- Guo, C. et al. RDMA over Commodity Ethernet at Scale (RoCE at Microsoft). SIGCOMM 2016.
- Shan, Y. et al. LegoOS: A Disseminated, Distributed OS for Hardware Resource Disaggregation. OSDI 2018.