惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

V
Visual Studio Blog
爱范儿
爱范儿
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
雷峰网
雷峰网
V
V2EX
博客园_首页
Engineering at Meta
Engineering at Meta
博客园 - 聂微东
Cyber Security Advisories - MS-ISAC
Cyber Security Advisories - MS-ISAC
Apple Machine Learning Research
Apple Machine Learning Research
GbyAI
GbyAI
H
Help Net Security
A
About on SuperTechFans
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Blog — PlanetScale
Blog — PlanetScale
W
WeLiveSecurity
云风的 BLOG
云风的 BLOG
D
Docker
Security Archives - TechRepublic
Security Archives - TechRepublic
Help Net Security
Help Net Security
N
News and Events Feed by Topic
Simon Willison's Weblog
Simon Willison's Weblog
G
Google Developers Blog
A
Arctic Wolf
T
The Blog of Author Tim Ferriss
博客园 - 叶小钗
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
Google DeepMind News
Google DeepMind News
博客园 - 三生石上(FineUI控件)
aimingoo的专栏
aimingoo的专栏
Hacker News: Ask HN
Hacker News: Ask HN
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
博客园 - 司徒正美
Threat Intelligence Blog | Flashpoint
Threat Intelligence Blog | Flashpoint
P
Privacy International News Feed
T
Troy Hunt's Blog
T
Tenable Blog
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Recorded Future
Recorded Future
F
Fortinet All Blogs
D
DataBreaches.Net
B
Blog
T
Threat Research - Cisco Blogs
MyScale Blog
MyScale Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
The GitHub Blog
The GitHub Blog
Security Latest
Security Latest
M
MIT News - Artificial intelligence

鸟窝

鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 鸟窝 LLM 究竟是如何工作的? Go 实验特性详解 amd64 微架构级别对 Go 程序性能提升多少? Loop Engineering 实践:一次批量实现 8 个 issue,完成夔牛工具的开发 Loop Engineering 实践:我把 RDMA 开发库移植到 Go 语言,花费 239 块钱 百度物理网络监控工具开源第二弹:毫秒级监控工具 baize,让你的网络问题无处遁形 百度网络监控工具开源第三弹:lidar — 不只是 pingmesh 别只盯着gopacket了,看看这个强大的网络库 大厂的内部工具居然开源了! 一窥百度物理网络秒级监控定位的秘密 如何构建你自己的 Agent 运行时 寻找你代码中的臭味:一个让 AI 帮你嗅出架构腐化的开源 Skill 套壳不丢人!我用Go+AI搓了一个Agent统一编排框架,ClaudeCode-Codex-Pi全被我包了 告别死锁和陈旧语法、告别性能瓶颈:三个开源 Skill,新手Gopher 秒变 Go 语言大神 SPEC和PRD的区别 SPEC和方案设计有什么区别 从需求到上线,让 AI 管理你的整个研发流程! antigravity-cli 使用 CLIProxyAPI, 让最新的 Codex 能够支持国内的各大模型 Clawpatch + codex-review:AI 代码审查工具链的正确打开方式 拆解Manus:沙盒架构深度解析 40+ Claude Code Tips: From Basics to Advanced Ralph 实验:构建 SQLite UI 了解 Manus Sandbox - 您的云计算机 Notex:一个开源 NotebookLM 替代方案的实现 拆解Manus:真正有用的深度报告的生成 Claude Code 使用 我给每个模型服务商『捐』了10块钱,只为了... Go之禅 - 基于Rob Pike思想的Go语言哲学 Codex CLI vs Gemini CLI vs Claude Code:谁是最佳选择? goskills:Claude Skills 功能强大,为我所用 一行代码使用 Claude Skill 和 deepseek langchain + MCP:如虎添翼 Linux 中网络包的一生 godotenv 库介绍 Codex CLI vs Gemini CLI vs Claude Code: Which is the Best? 使用Linux 30年了,我都不知道 ping 8.8 还能这么用? 从 AI 哪里挣钱? Go sync 包近两年发展综述 deepseek-v3.2-exp的闪电索引器
傻瓜式RDMA高性能网络开发:从零跑到 400 Gb每秒
smallnest · 2026-06-17 · via 鸟窝

用 Go 写 RDMA,到底能有多简单?又能有多快?这篇带你从零跑到 400 Gb/s。

开篇:一个让人又爱又怕的技术

如果你做过高性能网络,一定听过 RDMA 这个词。它是 AI 训练集群里 GPU 之间狂飙数据的底层、是分布式存储压榨延迟的杀手锏、是金融交易系统微秒必争的武器。

两种传输 & 两种操作

  • RC(可靠连接,类比 TCP):有序可靠,支持双边和单边操作

  • UD(不可靠数据报,类比 UDP):无连接,一对多

  • 双边操作(Send/Recv):接收方要先挂好接收请求,双方 CPU 都参与

  • 单边操作(RDMA Write/Read):发起方直接读写对端内存,对端 CPU 完全不参与——这是 RDMA 最"魔法"的地方


二 · 先用 perftest 摸清家底

在写代码之前,先得知道你的网卡能跑多快。业界标准是 perftest(linux-rdma 出品的 C 版基准工具)。gordma 贴心地用 Go 复刻了一套对标工具,放在 cmd/ 下:

工具 对标 测什么
go_send_bw / lat ib_send_bw/lat 双边 Send 的带宽 / 延迟
go_write_bw / lat ib_write_bw/lat 单边 Write
go_read_bw / lat ib_read_bw/lat 单边 Read
go_rdmanet_bw / lat —(高级) 测 gordma 高级 API

命名规律很简单:操作(send/write/read) + 指标(bw 带宽 / lat 延迟)。每个工具不带地址就是服务端,带对端地址就是客户端

跑一把带宽测试:

1
2
3
4
5
6
7
go build -o bin/ ./cmd/...


./bin/go_send_bw -s 65536 -n 1000000 -d mlx5_1 -x 3


./bin/go_send_bw -s 65536 -n 1000000 -d mlx5_1 -x 3 33.0.226.25:18515

输出:

1
2
#bytes    #iterations   BW average[MB/s]   MsgRate[Mpps]
65536 1000000 48996.54 0.747628

48996 MB/s ≈ 392 Gb/s(注意单位:go_send_bw 输出的是 MB/s=10⁶ 字节/秒,×8÷1000 才是 Gb/s),这就是这张 400G 网卡的实力基准。记住这个数,后面要拿它当标尺。

⚠️ 单位是个大坑:三个常用工具输出单位各不相同,直接比原始数会差出 8 倍——C 版 ib_send_bwMiB/s(2²⁰ 字节)、Go 版 go_send_bwMB/s(10⁶ 字节)、gordma 的 --rawMiB/s(已对齐 C 版)。本文所有数字都统一换算到 Gb/s(10⁹ bit) 再比较。

💡 小贴士:命令里的 IP 是服务端 -d 指定的那张 RoCE 网卡绑定的 IP,不是CPU网络/SSH 那个 IP。这是新手最容易连不上的坑。两端的 -d(设备)和 -x(GID 索引,RoCE v2 常用 3, 可以使用show_gids查看)要对齐同一张物理网络。


三 · 底层 API:完全掌控,但要写够样板

gordma 的底层包 gordma 一比一映射了 RDMA 的对象模型。想要完全掌控每个工作请求、每个 QP 参数,用它。代价是:你得自己走完那七步。

来看一个完整可跑的 RC 回显(用 rdma_cm 建连,省掉手写状态机):

服务端:收一条,回显

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
func server(addr string) error {
ln, _ := gordma.Listen(addr)
defer ln.Close()
cm, _ := ln.Accept()
defer cm.Close()
qp, cq, pd := cm.QP(), cm.CQ(), cm.PD()


mr, _ := pd.RegMRBuffer(4096, gordma.AccessLocalWrite)
defer mr.Close()


sge := gordma.SGEFromMR(mr, 0, 4096)
qp.PostRecv(gordma.RecvWR{WRID: 1, SGList: []gordma.SGE{sge}})


wc := make([]gordma.WorkCompletion, 1)
pollOne(cq, wc)
msg := mr.Bytes()[:wc[0].ByteLen]
fmt.Printf("got %q\n", msg)


copy(mr.Bytes(), msg)
qp.PostSend(gordma.SendWR{
WRID: 2, Opcode: gordma.OpSend,
SGList: []gordma.SGE{gordma.SGEFromMR(mr, 0, len(msg))},
Signaled: true,
})
pollOne(cq, wc)
return nil
}


func pollOne(cq *gordma.CQ, wc []gordma.WorkCompletion) {
for {
if n, err := cq.Poll(wc); err != nil || n > 0 {
return
}
}
}

每一行都对应一个 RDMA 概念:注册内存 → 先挂 recv → 轮询 CQ → post send。底层 API 的好处是没有任何隐藏行为,你能做单边 Write/Read、能精调 QP 容量、能复刻 perftest——坏处是,样板真的多。


四 · 高级 API:像写 net 一样写 RDMA

如果你只是想写业务,不想碰 MR、WR、CQ 这些——用 rdmanet 子包。它把上面那一大坨全收进了 Dial / Listen / SendMsg / RecvMsg

来看同样的事,高级怎么写。一个 RPC 服务:

服务端

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
func serve(addr string, opts []rdmanet.Option) error {
ln, err := rdmanet.Listen(addr, opts...)
if err != nil { return err }
defer ln.Close()
for {
conn, err := ln.Accept()
if err != nil { return err }
go handle(conn)
}
}

func handle(conn *rdmanet.Conn) {
defer conn.Close()
for {
req, err := conn.RecvMsg()
if err == io.EOF { return }
if err != nil { return }
conn.SendMsg(process(req))
}
}

客户端

1
2
3
4
5
conn, _ := rdmanet.Dial("33.0.226.25:18515",
rdmanet.WithDevice("mlx5_1"), rdmanet.WithGIDIndex(3))
defer conn.Close()
conn.SendMsg([]byte("hello"))
reply, _ := conn.RecvMsg()

没有 MR、没有 WR、没有 CQ 轮询、没有状态机。 是不是和标准库 net 一模一样?

rdmanet 还提供了一整套实用能力:

  • 消息语义 SendMsg/RecvMsg:保留边界,大消息自动分片重组
  • 字节流适配器 Read/Write:Conn 直接满足 io.ReadWriteCloser,能配 io.Copy 传文件
  • 批量 I/O SendBatch/RecvBatch:摊薄每次调用开销
  • UD 数据报 PacketConn:无连接、一对多
  • 地址注册表 Registry:带外发现对端

仓库里还附带了 17 个按功能拆分的示例(examples/ 目录),从最小回显到全双工聊天、文件传输、一对多广播,一个功能一个目录,照着抄就行。


五 · RawConn:既要 net 风格,又要榨干网卡

高级 Conn 好用,但有个问题:它为了"保留消息边界 + 流控 + 易用"付出了固定成本——封帧、信用流控、bounce 缓冲拷贝、后台 poller goroutine 的跨线程交接。这些叠加起来,让它在 64KB 大包上只能跑到约 28 Gb/s,远没喂满 400G 网卡。

于是 gordma 给了第三个选择:RawConn

它的理念很直接:把所有花哨的东西全剥掉,直接暴露"注册内存 + 投递 WR + 自己轮询 CQ",在同一个 goroutine 里 post + busy-poll,无封帧、无流控、无交接。这正是 perftest 打满线速的那套循环。

最省事的用法是内置的 PipelineBatch,保持 N 个请求 in-flight(同时在网卡里跑),每完成一个补一个:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
rc, _ := rdmanet.DialRaw(addr,
rdmanet.WithDevice("mlx5_1"),
rdmanet.WithGIDIndex(3),
rdmanet.WithQueueDepth(128))
defer rc.Close()

mr, _ := rc.RegisterMemory(size * txDepth)
defer mr.Close()

rc.PipelineBatch(iters, txDepth, func(wrID uint64) gordma.SendWR {
slot := int(wrID) % txDepth
return gordma.SendWR{
WRID: wrID,
Opcode: gordma.OpSend,
SGList: []gordma.SGE{gordma.SGEFromMR(mr, slot*size, size)},
Signaled: true,
}
})

RawConn 还支持:

  • 单边 Write/Read:走 TCP 握手交换了对端 rkey/地址,可以直接做"对端 CPU 不参与"的远程读写
  • 批量提交 PostSendBatch:用 WR 链表一次 ibv_post_send 提交多个请求,把 cgo 跨界开销从"每个 WR 一次"降到"每批一次",小包消息率因此能提升约一个数量级
  • 逃生舱 QP()/CQ()/PD():需要时随时下沉到底层自己驱动

代价当然有:RawConn 不替你保留消息边界、不做流控(得自己控制 in-flight 数,否则 RNR)、不托管缓冲区。一句话:先用 Conn,确实要榨干网卡时再上 RawConn

🔬 顺带破一个误解:很多人(包括我一开始)以为"Go 经 cgo 调 RDMA 一定比 C 慢一截"。我用 GORDMA_PROBE=1 把发送循环拆成"提交 WR(post)"和"忙等完成(poll)"两段实测,结论反直觉:一次 ibv_post_send 含 cgo 跨界约 300ns,只占总时间 ~15%,而且 go_send_bwRawConn 两者完全相同。也就是说——cgo 提交开销真实存在但很小,不是性能差距的主因。后面第六节会看到,go_send_bw 状态好时能直接追平 C 版 ib_send_bw,根本没有"Go 追不上 C"的固有差距。


六 · 真刀真枪:带宽压测对比

理论讲完,上数据。同一对 400G RoCE v2 节点,64KB 大包,100 万条消息,实测:

结论很清楚:

  • ConnRawConn,同一个库、同一张卡,吞吐 暴涨约 8 倍,证明那 28 Gb/s 的天花板就是高级那套便利机制的固定成本。
  • RawConn 用纯 Go(加薄薄一层 cgo)把吞吐推到了 230+ Gb/s 的量级,已经和同一个库的底层 go_send_bw 在同一个数量级。

一个反直觉的发现:差距不在 cgo,而且不是固定的

我原本想搞清"RawConn(232) 为什么比 go_send_bw(392) 慢约 1.7 倍",于是做了一组同机、同口径、交替跑的实验(锁核 taskset + 性能调频,尽量压住抖动),用 GORDMA_PROBE=1 拆出 post/poll。结果挖出三件事:

① cgo 提交不是瓶颈。 两个工具的 post(提交 WR)都是 ~300 ns/WR、占比 ~15%,完全相同。所谓"每个 WR 一次 cgo 跨界拖慢了 Go",在这个负载上站不住——提交很便宜,而且两边一样便宜。

② Go 能追平 C。 锁核后 go_send_bw 实测峰值 0.748 Mpps,和 C 版 ib_send_bw 完全一致。早先看到的"go_send_bw 只有 ~314 Gb/s"是机器状态差时的数,不是 cgo 的锅。

③ 差距是"可变"的,不是固定缺陷。 交替跑 3 轮,go_send_bw0.414 / 0.748 / 0.414 Mpps 之间离散双峰跳变,而 RawConn 稳定在 0.42。也就是说:go_send_bw 状态差的那几轮,和 RawConn 几乎持平;两者差距在 **1.05×1.76× 之间晃**,取决于那一轮 go_send_bw 能不能抢到干净的网卡/CPU 窗口。

差距的真正位置在 poll(忙等完成到达):go_send_bw 的 poll 在 0.75~1.33 µs/WR 间大幅波动(状态好就打满线速),RawConn 则被稳定压在 ~1.40 µs。考虑到这是一台共享 GPU 机、400G 链路被其他租户竞争,最合理的解释是环境竞争,而非 RawConn 有独立的代码缺陷——两个工具走的是同一套 QP 建立和 CQ 轮询路径,逐行核对没有能让 RawConn 单独变慢的差异。

🧭 给读者的实用结论:① 不要迷信"Go+cgo 必慢于 C",在大包带宽场景两者能打平;② cgo 的固定开销真实但小,真正要省它得靠批量提交 + 忙轮询(见下文小包测试);③ 想认真比性能,务必锁核、独占机器、多次取中位数,共享机上的单次数字会骗你。

小包更能看出批量提交的威力

64KB 大包很容易撞带宽上限,看不出 CPU 侧的优化。换成 1KB 小包(消息率受限场景):

1
./bin/go_rdmanet_bw --raw -s 1024 -n 5000000 -d mlx5_1 -x 3 -b 128 33.0.226.25:18515
1
raw-batch Send(txDepth=128): 5000000 x 1024 bytes in 0.85s: 47.92 Gb/s, 5.850 Mpps

5.85 Mpps——批量提交(PostSendBatch)在小包上把消息率拉高了一个数量级。这正是榨干高频小消息场景的关键。


尾声:三个档位,按需取用

gordma 最打动我的,是它没有逼你在"易用"和"性能"之间二选一,而是给了一条平滑的升级路径:

你的需求 用哪个 心智负担
写业务,要 net 风格 rdmanet.Conn 像写 socket,几行搞定
既要简单又要极限吞吐 rdmanet.RawConn 自己管内存,几十行
完全掌控每个细节 底层 gordma 复刻 perftest 的程度

而且全部代码在任何平台都能编译(macOS/Windows 走 stub 桩实现,RDMA 调用优雅返回 ErrNotSupported),只有真正运行时才需要 Linux + RDMA 硬件。这意味着你可以在 MacBook 上写代码、跑单元测试,真要压测时再丢到带卡的机器上,开发体验和门槛都友好得多。

如果你正在被 RDMA 编程劝退,或者想给你的 Go 服务接上高性能网络,不妨试试 gordma:

🔗 github.com/smallnest/gordma

go run ./examples/echo-msg 跑通第一个 RDMA 程序开始,你会发现——原来 RDMA 也可以这么"傻瓜"。


本文所有性能数据均为同一对 400G RoCE v2 节点上的实测结果,会随硬件与配置不同而变化。完整教程、API 文档、17 个示例和 8 个压测工具均在仓库中。