OSDI’21 The nanoPU-A Nanosecond Network Stack for Datacenters

OSDI’21 The nanoPU A Nanosecond Network Stack for Datacenters

论文链接:https://www.usenix.org/conference/osdi21/presentation/ibanez

摘要

nanoPU

nanoPU 是一种新的 NIC-CPU 协同设计,用于加速使用了很多微秒级小型 RPC 的应用程序。nanoPU 的新颖之处在于设计了网络和应用程序之间的快速路径——绕过高速缓存和内存层次结构,并将到达的消息直接放入 CPU寄存器文件中。这条快速路径包含对低延迟传输和拥塞控制的可编程硬件支持,以及对 RPC 到核心的高效负载平衡的硬件支持。

性能

  • 通过 nanoPU 的线对线 RPC 响应时间仅为 69ns,比同类最佳的低延迟商用 NIC 快一个数量级。
  • 相对于传统线程调度技术,硬件线程调度程序能够将 RPC 尾部响应时间降(长尾延迟?)低约 5 倍,同时使系统能够承受高 20% 的负载。

长尾延迟:

假设Redis处理了100个请求,99个请求的响应时间都是1s,而有一个请求的响应时间是100s。那么,如果看平均延迟,这100个请求的平均延迟是1.99s,但是对于这个响应时间是100s的请求而言,它对应的用户体验将是非常糟糕的。如果有100万个请求,哪怕只有1%的请求是100s,这也对应了1万个糟糕的用户体验。这1%的请求延迟就属于长尾延迟。

简介

着手回答的问题

处理 nanoRequest 可以达到的绝对最小开销是多少?

nanoRequests 只是非常短暂的 RPC,由客户端和服务器 NIC 标记以进行特殊处理。在 nanoPU 中,nanoRequests 通过 NIC 遵循一条新的低开销路径,绕过操作系统和内存缓存层次结构并直接到达运行线程的寄存器。所有消息重组功能、传输和拥塞控制逻辑都移至硬件,线程调度和核心选择决策也是如此。传入的 nanoRequest 在到达应用程序代码之前仅通过硬件。nanoPU 原型可以在不到 40ns 的时间内将到达的 nanoRequest 传送到正在运行的应用程序线程中(如果绕过以太网 MAC,则不到15ns)

能否通过在确定的时间内处理 nanoRequests 来最小化尾部响应时间?

由于 nanoRequests 由固定延迟的硬件管道处理,如果单个数据包请求到达等待核心,其线程将始终在小于 40ns 的时间内开始处理消息。另一方面,如果核心很忙,或者另一个请求在前面排队,那么处理可能会延迟。在这种情况下,新型硬件线程调度器可以在特定假设下限制尾部响应时间。

nanoPU 的设计

pipeline

网络路径

nanoPU 有两条独立的网络路径:

传统的 DMA 路径

主机的 L1C 或 LLC。

传统路径可以是通过硬件和软件的任何现有路径;因此,所有网络应用程序都可以在 nanoPU 的传统路径上运行而不会发生变化,并且性能至少与今天一样好。

加速路径

直接访问寄存器

  • 硬件线程调度器(HTS)
  • 两个用于网络入口和出口数据的小 FIFO 存储器
  • 两个保留的通用寄存器(GPR):一个作为出口 FIFO 的尾部,用于发送 nanoRequest 数据,另一个作为用于接收的入口 FIFO 的头部。
  • CPU 内核静态分为两组:运行普通应用程序的内核和运行nanoRequest 应用程序的内核。运行常规应用程序的内核使用标准操作系统软件线程调度,操作系统会将 nanoRequest 线程的调度委托给 HTS。

路径介绍:

  1. 数据包到达并进入 P4 可编程PISA 管道。除了标准的报头处理(例如,匹配 IP 地址、检查版本和校验和以及删除隧道封装)之外,管道还使用匹配操作表 2 检查传输报头中的目标第 4 层端口号,以决定是否消息应该沿着快速路径传送。如果是,它继续到 2,否则它遵循通常的 DMA 处理路径 D。
  2. 数据包被重组为消息;为整个消息分配一个缓冲区,并且(可能)将数据包数据重新排序为正确的顺序。
  3. 传输协议保证消息可靠到达;在所有数据到达之前,消息数据和信令数据包将根据协议与对等方交换。
  4. 当消息到达时,它被放置在每个应用程序的接收队列中,等待由核心选择逻辑分配给核心。
  5. 轮到某个消息时,它被发送到指定内核上适当的每线程入口 FIFO,等待HTS提醒内核运行消息的线程并将第一个字放入 netRX 寄存器。
  6. 核心处理数据,如果运行服务器应用程序,通常会为客户端生成响应消息。
  7. 应用程序通过发出一次将一个”字”写入 netTX 寄存器的指令来传输消息,其中字的大小由 CPU 寄存器的大小定义,通常为 64 位 (8B)。
  8. 消息词流入全局传输队列。
  9. 在通过出口 PISA 管道离开之前,消息被分成数据包。

Protocol Independent Switch Architecture(PISA):协议独立交换架构。包括三个主要组成部分。第一个是解析器(Parser),通过编程定义哪些报头字段(以及在包中的位置)将被后面的阶段识别和匹配。第二个是匹配操作单元(Match-Action Unit),每个单元都被编程来匹配(并可能对其进行操作)一个或多个已标识的报头字段。第三个是编码器(Deparser),将包元数据重新序列化到包中,然后在输出链路上传输。deparser根据之前处理阶段缓存在内存中的所有报头字段重新构建在链路上传输的每个包。

structure

线程安全的寄存器文件接口

痛点

最近的工作表明,对于小数据包,PCIe 延迟贡献了大约 90% 的 wire-to-wire 响应时间(800 - 900ns)。

解决方案

几位作者提议将 NIC 与 CPU 集成,以将数据包直接带入缓存。而 nanoPU 更进一步,将网络快速路径直接连接到CPU 内核的寄存器文件。这样我们可以允许应用程序通过一次向/从一对专用 CPU 寄存器写入/读取一个字 (8B)来发送和接收网络消息。

这样做的优点:

  • 消息数据绕过内存和缓存层次结构,最大限度地缩短了数据包从到达线路到可用于处理的时间
  • 减少处理时间的可变性,从而最大限度地减少尾部响应时间。
  • 由于 nanoRequests 在专用 FIFO 中缓冲,与缓存分开,nanoRequest 数据不会与其他应用程序数据竞争缓存空间,从而进一步减少应用程序的缓存未命中率

工作原理

nanoPU 在寄存器文件中为网络 IO 预留了两个通用寄存器(GPR),我们称之为 netRX 和 netTX。当应用程序发出一条从 netRX 读取的指令时,它实际上是从网络接收队列的头部读取一个消息字。类似地,当应用程序发出写入 netTX 的指令时,它实际上将消息字写入网络传输队列的尾部。网络接收和发送队列存储在直接连接到寄存器文件的小型 FIFO 存储器中。除了保留的 GPR 外,还有一小组控制和状态寄存器(CSR)用于核心和 NIC 硬件相互协调。

  • 定界消息
    应用程序发送和接收的每条消息都以固定的 8B “应用程序标头” 开头。在到达的消息中,此标头指示消息长度(以及源 IP 地址和第 4 层端口号),它允许软件识别消息的结尾。类似地,离开消息的应用程序报头包含消息长度(以及目标 IP 地址和第 4 层端口号),以便 NIC 可以检测到传出消息的结尾。
  • 线程安全
    我们需要防止错误的线程读取或写入另一个线程的消息。 nanoPU 使用硬件互锁来防止这种情况。它为每个线程维护一个单独的入口和出口 FIFO,并控制对 FIFO 的访问,以便 netRX 和 netTX始终分别映射到当前运行线程的 FIFO 的头部和尾部。
  • 软件修改
    寄存器文件可以在一个 CPU 周期内访问,而 L1 高速缓存通常需要三个周期。如果应用程序线程可以通过串行读取 netRX 直接从入口 FIFO 处理数据,那么它会运行得更快。

硬件中的线程调度

痛点

当前若需要运行低延迟的应用程序,通常的做法是将其固定在一个专用内核上,但这在线程空闲时是十分低效的。另一种方法是将一个内核专门用于运行软件线程调度程序来管理其他核心。而软件调度程序需要定期运行来避免被中断和相关开销所淹没,这就需要为其指定一个频率。若太快则会浪费资源,太慢则会出现不必要的线程延迟。

解决方案

将 nanoRequest 线程调度器转移到硬件上,这样可以持续监控消息处理状态以及网络接收队列,并做出亚纳秒级的调度决策。

工作原理

每个内核都包含自己的调度程序硬件。当一个新线程初始化时,它必须通过绑定到第 4 层端口号并选择严格的优先级(0 是最高)来向其核心的 HTS 注册自己。第 4 层端口号让 nanoPU 硬件区分线程,并确保netRX 和 netTX 始终是当前运行线程的 FIFO 的头部和尾部。

HTS 跟踪正在运行的线程的优先级及其在 CPU 内核上花费的时间。当一条新消息到达时,如果它的目标线程的优先级低于或等于当前线程,则新消息被排队。如果传入消息是针对更高优先级线程的,则正在运行的线程将被挂起并将目标线程交换到核心上。每当 HTS确定必须交换线程时,它断言一个新的特定于NIC 的中断,该中断会陷入一个小型软件中断处理程序(仅在相关内核上),并且通过将目标的第 4 层端口号写入专用 CSR来告诉中断处理程序要切换到哪个线程。

  • 调度策略
    HTS 实施有界的严格优先级调度策略,以确保具有待处理工作的最高优先级线程始终在核心上运行。线程被标记为活动或空闲。如果线程符合调度条件,则它被标记为活动,这意味着它已被注册(端口号和 RX/TX FIFO 已分配)并且消息正在线程的 RX FIFO中等待。该线程保持活动状态,直到它明确指示它处于空闲状态且其 RX FIFO 为空。 HTS 试图确保最高优先级的活动线程始终运行。
  • 有限响应时间
    HTS 支持限制一个高优先级应用程序可以阻止另一个应用程序多长时间。如果优先级为 0 的线程处理一条消息的时间超过 t0,调度程序将立即将其优先级从 0 降为 1,允许它被另一个具有待处理消息的优先级为 0 的线程抢占。

nanoPU 中的 NIC 管线

nanoPU 快速路径的 NIC 部分由两个主要组件组成:可编程传输层和内核选择算法。

工作原理

  • 可编程传输层
    nanoPU 为 nanoRequest 线程提供了一种可靠的单向消息服务。为了足够快,传输层需要在NIC 的硬件中终止。可编程传输层的核心是事件驱动的 P4 可编程 PISA 管道。可以对管道进行编程以执行正常的报头处理。我们增强它以实现可靠的消息处理,包括拥塞控制,并对其进行编程以实现低延迟消息协议。
  • CPU 内核选择
    如果 NIC 随机向核心发送消息,一些消息将不可避免地排在队列中等待一个繁忙的核心,而另一个核心则处于空闲状态。因此,NIC 在硬件中实现了核心选择算法。NIC 使用 Join-Bounded-Shortest-Queue 或 JBSQ(n) 算法跨内核负载平衡 nanoRequest 消息。 JBSQ(n) 使用集中式队列以及每个核心的最大深度为n的短边界队列的组合。当每个核心队列有可用空间时,集中式队列将首先补充最短队列。JBSQ(1)等效于单队列模型。

finish

评估

exp