RDMA - inline 内联提高小包性能-降低时延(减少两个 PCIe 往返延迟)

背景知识 - IB与PCIE是如何工作的?

摘要

这篇文章描述了通过 PCI Express 结构在 CPU 和 NIC 之间发生的一系列协调事件,以通过 InfiniBand 互连传输消息并发出其完成信号。

通过 InfiniBand 发送消息的主要方法是通过 Verbs API。libibverbs 是此 API 的标准实现,由 Linux-RDMA 社区维护。Verbs 中有两种函数:慢速路径函数和快速路径函数。慢速路径函数(例如 ibv_open_device、ibv_alloc_pd 等)与资源(例如上下文、保护域和内存区域)的创建和配置有关。它们之所以被称为“慢速”,是因为它们涉及内核,因此会产生上下文切换的昂贵开销。快速路径函数(例如 ibv_post_send、ibv_poll_cq 等)处理操作的启动和完成。它们之所以被称为“快速”,是因为它们绕过内核,因此比慢速路径函数快得多。通信的关键路径主要由快速路径函数组成,有时还包括慢速路径函数(例如 ibv_reg_mr),用于动态注册内存区域(取决于通信中间件)。这篇文章重点介绍程序员执行 ibv_post_send 后发生的机制。

快速 PCIe 背景

网络接口卡 (NIC) 通常通过 PCI Express (PCIe) 插槽连接到服务器。PCIe I/O 子系统的主要导体是根复合体 (RC)。RC 将处理器和内存连接到 PCIe 结构。PCIe 结构可能由设备层次结构组成。连接到 PCIe 结构的外围设备称为 PCIe 端点。PCIe 协议由三层组成:事务层、数据链路层和物理层。第一层,即最上层,描述发生的事务类型。对于这篇文章,两种类型的事务层数据包 (TLP) 是相关的:MemoryWrite (MWr) 和 Memory Read (MRd)。与独立的 MWr TLP 不同,MRd TLP 与来自目标 PCIe 端点的带数据完成 (CplD) 事务相结合,其中包含发起者请求的数据。数据链路层使用数据链路层数据包 (DLLP) 确认 (ACK/NACK) 和基于信用的流控制机制确保所有事务的成功执行。只要发起者有足够的信用额度,它就可以发起事务。当它从邻居那里收到更新流控制 (UpdateFC) DLLP 时,它的信用额度就会得到补充。这种流控制机制允许 PCIe 协议有多个未完成的事务

涉及的基本机制

首先,我将描述如何使用完全卸载方法发送消息,即 CPU 仅通知 NIC 有消息要传输;NIC 将完成传输数据的其他所有工作。在这种方法中,剩余CPU 可用于计算活动(业务)。但是,这种方法可能会损害小消息的通信性能(很快就会变得明显)。为了提高此类情况下的通信性能,InfiniBand 提供了某些操作功能,我将在下一节中描述这些功能。

从 CPU 程序员的角度来看,存在一个传输队列(Verbs 中的发送队列是队列对 (QP))和一个完成队列(Verbs 中的 CQ 的长格式)。用户将其消息描述符(Verbs 中的 MD;工作队列元素/条目 (WQE;wookie))发布到传输队列,然后轮询 CQ 以确认已发布消息的完成。用户还可以请求通过中断通知完成情况。但是,轮询方法是面向延迟的,因为关键路径中没有上下文切换到内核。网络上消息的实际传输是通过处理器芯片和 NIC 之间的协调进行的,使用内存映射 I/O (MMIO) 和直接内存访问 (DMA) 读写。我将在下图下方描述这些步骤

img

步骤 0:用户首先将消息描述符 MD 排队到 TxQ 中。然后,网络驱动程序准备包含 NIC 标头和指向有效负载的指针的设备特定 MD。

步骤 1:使用 8 字节原子写入内存映射位置,CPU(网络驱动程序)通知 NIC 已准备好发送消息。这称为敲响门铃(DoorBell)。PCIE的RC 使用 MWr PCIe 事务执行门铃。

步骤 2:门铃响后,NIC 使用 DMA 读取获取 MD。MRd PCIe 事务执行 DMA 读取, 大小为64字节。

步骤 3:然后,NIC 将使用另一个 DMA 读取(另一个 MRd TLP)从已注册的内存区域获取有效负载。请注意,在 NIC 执行 DMA 读取之前,必须将虚拟地址转换为其物理地址。

步骤 4:一旦 NIC 收到有效负载,它就会通过网络传输读取的数据。成功传输后,NIC 会收到来自目标 NIC 的确认 (ACK)。

步骤 5:收到 ACK 后,NIC 将通过 DMA 写入(使用 MWr TLP)完成队列条目 (CQE;在 Verbs 中又称为 cookie;在 Mellanox InfiniBand 中为 64 字节)到与 TxQ 关联的 CQ。然后 CPU 将轮询此完成以取得进展。

总之,每个提交的关键数据路径需要一次 MMIO 写入(CPU通知网卡的DoorBell)、两次 DMA 读取(先读描述符,再读数据)一次 DMA 写入(写入发送完成CQE)。DMA 读取转换为昂贵的往返 PCIe 延迟。例如,ThunderX2 机器的往返 PCIe 延迟约为 125 纳秒。

操作功能 内联Inline、Postlist、无信号完成(Unsignaled Completions)和编程 I/O(Programmed I/O ) 是 IB 的操作功能,有助于减少这种开销。我将在下文中描述它们,其中考虑 QP 的深度为 n。

Postlist:IB 允许应用程序通过一次调用 ibv_post_send 来发布 WQE 链接列表,而不是每次 ibv_post_send 只发布一个 WQE。它可以将 DoorBell 响铃次数从 n 减少到 1。

内联:发送数据时,CPU(网络驱动程序)将数据复制到 WQE 中。因此,通过对 WQE 的第一次 DMA 读取,NIC 也会获得有效负载,从而消除了对有效负载的第二次 DMA 读取。

未发出信号的完成:IB 允许应用程序关闭 WQE 的完成,而不是为每个 WQE 发出完成信号,前提是每 n 个 WQE 中至少有一个发出信号(CQE)。关闭完成会减少 NIC 对 CQE 的 DMA 写入。此外,应用程序轮询更少的 CQE,从而减少取得进展的开销。

BlueFlame:BlueFlame 是 Mellanox 的编程 I/O 术语——它与 DoorBell 一起写入 WQE,从而切断 WQE 本身的 DMA 读取。请注意,BlueFlame 仅在没有 Postlist 的情况下使用。使用 Postlist,NIC 将 DMA 读取链接列表中的 WQE。

为了减少 PCIe 往返延迟的开销,开发人员通常将 Inlining 和 BlueFlame 一起用于小消息。它消除了两个 PCIe 往返延迟。虽然 Inlining 和 BlueFlame 的使用取决于消息大小,但 Postlist 和 Unsignaled Completions 的使用主要取决于用户的设计选择和应用程序语义。

Nvidia RDMA inline receive - Nvidia RDMA 内联接收

当 Inline-Receive 处于活动状态时,HCA 可以将接收到的数据写入接收 WQECQE。使用 Inline-Receive 可节省 PCIe 读取事务,因为 HCA 不需要读取散列表SGL,因此在接收短消息的情况下可提高性能(降低时延)。在轮询 CQ 时,驱动程序将接收到的数据从 WQE/CQE 复制到用户的缓冲区。因此,除了查询 Inline-Receive 功能和 Inline-Receive 激活之外,该功能对用户应用程序是透明的

Inline Receive Experimental Verbs - 内联接收的实验VerbsAPI

  • ibv_exp_query_device - 查询设备内联接收的大小
    • inline_recv_size
  • ibv_exp_create_qp - 创建QP
    • max_inl_recv
  • ibv_exp_create_dct - 创建动态连接目标端
    • inline_size

Inline Receive Experimental Device Attributes - 设备内联接收属性

IBV_EXP_DEVICE_ATTR_INLINE_RECV_SZ

Inline Receive RDMA-Core Verbs - 创建QP时允许内联接收

  • mlx5dv_create_qp
    • MLX5DV_QP_CREATE_ALLOW_SCATTER_TO_CQE

Inline Receive RDMA-Core Environmental Variables - 开启散列小包到CQE的环境变量

MLX5_SCATTER_TO_CQE

Relevant Man Pages - 更多关于散列/内联的内容请参考创建QP的文档

mlx5dv_create_qp: https://github.com/linux-rdma/rdma-core/blob/master/providers/mlx5/man/mlx5dv_create_qp.3.md

MLX5DV_QP_CREATE_DISABLE_SCATTER_TO_CQE - 禁用散列到完成队列元素CQE

如果 IOVA 与进程的 VA 不匹配,并且消息有效负载大小足够小以触发散射到 CQE 功能,则应设置MLX5DV_QP_CREATE_DISABLE_SCATTER_TO_CQE 标志。当使用设备内存时,不应使用 IBV_SEND_INLINE 和散射到 CQE,因为无法进行 memcpy

代码分析

rdma-core用户态驱动项目及内核态驱动

代码语言:javascript

复制

 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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
wr.send_flags = IBV_SEND_SIGNALED | IBV_SEND_INLINE //下发WQE前开启发送内联功能
irdma_uk_inline_send //以E810代码为例
    quanta = qp->wqe_ops.iw_inline_data_size_to_quanta(total_size)
    FIELD_PREP(IRDMAQPSQ_INLINEDATALEN, total_size)  //设置内联数据长度和标记,稍后通知硬件
    FIELD_PREP(IRDMAQPSQ_INLINEDATAFLAG, 1)
    qp->wqe_ops.iw_copy_inline_data((__u8 *)wqe, op_info->sg_list, op_info->num_sges, qp->swqe_polarity) -> irdma_copy_inline_data
        __u8 inline_valid = polarity << IRDMA_INLINE_VALID_S
        memcpy(wqe, cur_sge, bytes_copied) // 将用户数据拷贝到WQE

scatter 散列相关标记
MLX5DV_QP_CREATE_DISABLE_SCATTER_TO_CQE // 禁止散列到完成队列元素
MLX5DV_QP_CREATE_ALLOW_SCATTER_TO_CQE  // 允许散列到完成队列元数


nvidia inline
env = getenv("MLX5_SCATTER_TO_CQE") //设置环境变量, 允许散列到完成队列元素
MLX5_QP_FLAG_SCATTER_CQE
MLX5_QP_FLAG_ALLOW_SCATTER_CQE


轮询完成队列
mlx5_poll_cq -> poll_cq
    mlx5_stall_cycles_poll_cq
    or mlx5_stall_poll_cq
    mlx5_poll_one
        mlx5_get_next_cqe -> 添加惰性CQ轮询目前当用户想要轮询CQ是否完成时他别无选择只能获得整个工作完成WC)。 这有几个含义 - 例如 * 扩展 WC 是有限的因为添加新字段会使 WC 更大并且可能占用更多缓存行 * 每个字段都被复制到 WC - 甚至是用户不关心的字段 此补丁添加了对以惰性方式处理 CQE 的一些支持 新的惰性模式将在下游补丁中调用 我们只解析必需的字段以便找出 CQE例如类型状态wr_id 等为了与遗留模式共享代码而不影响性能对遗留代码进行了重构并使用了always_inline机制以便 分支条件将在编译时被删除
            next_cqe_sw
                ...
                return cq->active_buf->buf + n * cq->cqe_sz
            VALGRIND_MAKE_MEM_DEFINED -> memory check
            dump_cqe
        mlx5_parse_cqe(cq, cqe64, cqe, cur_rsc, cur_srq, wc, cqe_ver, 0) -> 多分支函数
            opcode = mlx5dv_get_cqe_opcode(cqe64)
            switch (opcode)
            mlx5_handle_error_cqe
                case MLX5_CQE_SYNDROME_TRANSPORT_RETRY_EXC_ERR:
                    return IBV_WC_RETRY_EXC_ERR
            case MLX5_CQE_REQ
                if (lazy)
                    case MLX5_OPCODE_RDMA_READ:
                        goto scatter_out -> 散开/内联相关
                    if (cqe64->op_own & MLX5_INLINE_SCATTER_32) -> or MLX5_INLINE_SCATTER_64
                        mlx5_copy_to_send_wqe(mqp, wqe_ctr, cqe, wc_byte_len)
                            scat = mlx5_get_send_wqe(qp, 0)
                                qp->sq_start + (n << MLX5_SEND_WQE_SHIFT)
                            copy_to_scat(scat, buf, &size, max, ctx)
                                if (likely(scat->lkey != ctx->dump_fill_mkey_be)) -> mlx5添加对 ibv_alloc_null_mr 的支持如果支持mlx5_alloc_null_mr 会分配 MR 并使用 mlx5_context 中的 dump_fill_mkey 设置其 lkey 成员此 MR 中的 dump_fill_mkey 稍后会在 ibv_post_receive() 和 ibv_post_send() 中用于分别排除使用转储和填充内存键设置的 ibv_sge 的写入和读取
                                    memcpy((void *)(unsigned long)be64toh(scat->addr), buf, copy)  // 拷贝数据
    update_cons_index
    mlx5_get_cycles
                                  
...
if (cqe->op_own & MLX5_INLINE_SCATTER_64)
  mlx5_copy_to_recv_wqe // 接收工作队列请求元素时,拷贝数据
      scat = get_recv_wqe(qp, idx)
          qp->buf.buf + qp->rq.offset + (n << qp->rq.wqe_shift)

总结

  • 应用开启小包内联后, 由驱动来完成内存拷贝
  • 拷贝有三种形态: 将用户发送的小包拷贝到发送WQE, 将完成的数据拷贝到CQE, 将接收的小包拷贝到接收WQE

参考

IB与PCIE是如何工作的(发包流程): https://www.rohitzambre.com/blog/2019/4/27/how-are-messages-transmitted-on-infiniband

Nvidia inline receive: https://docs.nvidia.com/networking/display/rdmacore50/inline+receive

create_qp: https://github.com/linux-rdma/rdma-core/blob/master/providers/mlx5/man/mlx5dv_create_qp.3.md

【RDMA】IBV_SEND_INLINE和IBV_SEND_SIGNALED的原理|RDMA小消息通信性能优化 https://blog.csdn.net/bandaoyu/article/details/119207147

【RDMA】InfiniBand如何工作和小消息通信性能优化方案: https://blog.csdn.net/bandaoyu/article/details/119204643

https://www.rohitzambre.com/blog/2019/4/27/how-are-messages-transmitted-on-infiniband

晓兵(ssbandjl)

博客: https://cloud.tencent.com/developer/user/5060293/articles | https://logread.cn | https://blog.csdn.net/ssbandjl | https://www.zhihu.com/people/ssbandjl/posts

https://chattoyou.cn(吐槽/留言)

DPU专栏

https://cloud.tencent.com/developer/column/101987

技术会友: 欢迎对DPU/智能网卡/卸载/网络,存储加速/安全隔离等技术感兴趣的朋友加入DPU技术交流群

weixin: ssbandjl

公众号: 云原生云

云原生云