本文通过一个连续提交 RDMA WRITE 的例子,浅谈 Selective Signaling(选择性完成通知)。

为便于理解,下面只讨论同一个 RC QP 的发送队列,假设所有请求都成功提交并正常完成。本文不展开错误恢复和资源管理等工程细节。

What

先把最容易混淆的一点说清楚:

1
2
3
一个 RDMA 操作已经完成
≠
应用程序一定会收到这个操作单独对应的完成记录

RDMA 中,应用提交的工作请求称为 WR(Work Request)。网卡可以向 CQ(Completion Queue,完成队列) 写入 CQE(Completion Queue Entry,完成记录),应用通过读取这些记录获知完成情况。

Selective signaling 的意思是:

不要求每个 WR 成功完成后都产生 CQE,而是只让选定的 WR 产生成功完成记录。

可以简单理解为:

1
2
3
4
5
signaled WR:
成功完成 → 产生 CQE

unsignaled WR:
成功完成 → 不产生 CQE

所以:

unsignaled 不是“不执行”,而是“成功完成时不单独报告”。

这里的“通知”指本地的完成记录,不是远端应用程序的回复。

Why

完成通知本身也有开销。

网卡需要写入 CQE,CPU 需要读取和处理 CQE。这些动作也会消耗 PCIe 带宽和处理资源。

假设应用连续提交 8 个 WR:

1
2
3
4
5
每个 WR 都报告完成:
8 个 WR → 8 条 CQE

每 4 个 WR,只让最后一个报告完成:
8 个 WR → 2 条 CQE

第二种方式依然执行全部 8 个操作,但减少了生成和处理完成记录的工作。

因此,Selective signaling 的目的可以概括为:

减少完成通知的开销,而不是减少 RDMA 操作本身。

基础 Example

假设向同一个 QP 的 SQ(Send Queue,发送队列)依次提交 8 个 RDMA WRITE:

1
WR1 → WR2 → WR3 → WR4 → WR5 → WR6 → WR7 → WR8

全部 signaled

每个 WR 成功完成后,都产生自己的 CQE:

1
2
3
4
5
6
7
8
WR1(S) → CQE1
WR2(S) → CQE2
WR3(S) → CQE3
WR4(S) → CQE4
WR5(S) → CQE5
WR6(S) → CQE6
WR7(S) → CQE7
WR8(S) → CQE8

这里 S 表示 signaled。

应用最终需要处理 8 条完成记录。

使用 selective signaling

只将 WR4、WR8 设置为 signaled,其余设置为 unsignaled:

1
2
3
4
5
6
7
8
9
10
11
同一个 QP 的发送队列:

WR1(U) → WR2(U) → WR3(U) → WR4(S)
│
▼
CQE4

WR5(U) → WR6(U) → WR7(U) → WR8(S)
│
▼
CQE8

这里 U 表示 unsignaled。

在本文假设的正常完成情况下,CQ 中只会出现:

1
CQE4、CQE8

前面没有 CQE 的 WR,如何确认完成?

关键在于:

对本例同一个发送队列中按序提交的 RDMA WRITE,后一个 WR 的成功完成,可以作为前面这些 WR 已完成的确认依据。

因此,应用读取到 WR4 的成功完成记录后,就可以确认:

1
WR1、WR2、WR3、WR4 都已经完成

读取到 WR8 的成功完成记录后,则可以继续确认:

1
WR5、WR6、WR7、WR8 也已经完成

可以把 WR4、WR8 理解为两个“完成进度标记”:

1
2
3
收到 CQE4:完成进度已经到达 WR4

收到 CQE8:完成进度已经到达 WR8

需要区分:CQE4 仍然只对应 WR4,并不是其中打包了四份完成结果。

应用之所以能够确认前面的请求也已完成,是因为同一发送队列上的完成顺序提供了依据,而不是因为网卡补发了前面三个请求的 CQE。

How

只看两个设置。

创建 QP 时,将 sq_sig_all 设置为 0,允许应用逐个 WR 选择是否请求成功完成记录:

1
qp_init_attr.sq_sig_all = 0;

然后,为需要报告完成的 WR 设置 IBV_SEND_SIGNALED。下面仅展示与 signaling 有关的设置:

1
2
3
4
wr[0].send_flags = 0;                 // WR1:unsignaled
wr[1].send_flags = 0; // WR2:unsignaled
wr[2].send_flags = 0; // WR3:unsignaled
wr[3].send_flags = IBV_SEND_SIGNALED; // WR4:signaled

这样,前面三个 WR 成功完成时不单独报告,第四个 WR 成功完成时产生 CQE。

“每 4 个通知一次”只是本文的示例,并不是固定要求。

一个简单的取舍

通知变少,也意味着应用可能更晚才知道前面的请求已经完成。

例如 WR1 已经完成,但应用要等到读到 CQE4,才通过这个完成记录确认它已经完成。

因此:

1
2
3
4
5
通知更频繁:
更及时地得知各个请求完成,但要处理更多 CQE

通知更稀疏:
处理的 CQE 更少,但确认前面请求完成的时间可能更晚

注意,更晚得知完成,不等于操作本身更晚完成。

记忆图

1
2
3
4
5
6
7
8
9
10
11
12
                 同一个 QP 的发送队列

WR1(U) → WR2(U) → WR3(U) → WR4(S)
│ │ │ │
▼ ▼ ▼ ▼
完成 完成 完成 完成
│
▼
CQE4
│
▼
应用确认 WR1~WR4 均已完成

可以简单记成:

1
2
3
4
5
操作:每个都执行

通知:只选部分报告

确认:借助同一发送队列的完成顺序,批量确认完成

一句话概括:

每个操作都要完成,但不必每个操作都单独报告完成。


参考资料:

  1. RDMA-aware Networks Programming Guide
  2. ibv_create_qp(3)
  3. ibv_post_send(3)
  4. Design Guidelines for High Performance RDMA Systems
  5. rdma-core:mlx5 Completion Queue 实现