在 FPGA 上运行 eBPF XDP 应用

2021-10-20 11:48:37 浏览数 (1)

这篇文章是对 OSDI20 的 Best Paper hXDP: Efficient Software Packet Processing on FPGA NICs 的阅读笔记,感兴趣的同学可以点击阅读原文查看论文的 paper,slide 和 video。

FPGA 目前已经成为数据中心和公有云进行硬件网络加速的一个重要手段,而 XDP 是 Linux 社区提供的一个通过 eBPF 进行高性能网络编程的软件框架。乍一看两者分别运行在不同的层面应该是井水不犯河水,而论文的作者把这一硬一软的两个黑魔法结合了起来,而这也激起了我阅读的兴趣。

为什么要把两者结合起来?

首先要回答的问题就是为什么要让 XDP 程序运行在 FPGA 上?理论上高速网络的功能,例如防火墙、负载均衡、SDN 独立使用 FPGA 或者 XDP 都是可以完成的,没必要硬把两者合在一起。只是为了叠 buff 把两者合在一起炫技,那成本显然太高了,必须有落地的场景才有意义。而这个工作的主要意义就是为了解决 XDP 和 FPGA 各自在处理网络方面所面临的问题。

从 XDP 一侧来看,XDP 是一种利用 CPU 来进行网络处理的技术,就会面临 CPU 带来的瓶颈:

  1. CPU 面临摩尔定律失效的问题,性能无法再翻倍增长。而网卡带宽目前仍在飞速增长,目前已经出现 200G 带宽的网卡,这势必需要消耗更多的 CPU 资源来进行 XDP 程序的运行
  2. CPU 资源如果用来处理业务逻辑会带来更高的价值收益,大量 CPU 资源用来处理网络数据包消费比会比较低

而 FPGA 的问题主要在于 FPGA 的编程使用的是硬件设计语言,和软件开发逻辑相差较大,有较高的门槛。除非是大型的企业的基础设施部门,普通企业内部很难有专门的硬件工程师来进行网络功能开发。

既然 XDP 一侧面临 CPU 资源消耗的问题,而 FPGA 一侧面临编程困难的问题,那么自然就产生了将 XDP offload 到 FPGA 的想法。这样一方面可以用较低的门槛,使用软件的方式灵活进行网络开发,另一方面又能把网络任务在 FPGA 上执行,能够降低延迟和 PCIe 的带宽,还能节省出更多的 CPU 资源给更高价值的业务使用。利用两者优势的同时还恰好补足了双方的短板,岂不美哉。

面临的挑战

理论上看只要在 FPGA 上实现一个能够运行 eBPF 指令的 IP core,在通过 Linux 已有的机制把指令 offload 到硬件上就可以了,但实际应用中会碰到另一个性能问题,那就是 FPAG 相对 CPU 较低的频率。作者使用的 FPGA 主频为 150Mhz,相对服务器 2Ghz ~ 3Ghz 有着数量级上的差别,这就会带来以下的问题:

  1. eBPF 是为 CPU 处理设计的指令集,包含大量的顺序执行,无法很好的并行化
  2. FPGA 较低的主频会导致顺序处理复杂 XDP 应用的延迟大幅上升
  3. 由于 FPGA 上资源有限,如果单个 IP Core 性能太差会导致整体吞吐量大幅低于使用 CPU 进行处理。

解决方法

接下来作者主要介绍如何针对 eBPF 指令在 FPGA 上运行进行优化。和软件开发总想着绕过这绕过那不同,硬件开发者的优化的方向都很硬核:

  1. 扩展指令集,根据网络处理的特点,通过自己的编译器将 eBPF 翻译成更合适的质量来大幅减少最终执行的指令数量
  2. 对 XDP 程序进行静态分析,编译成尽可能并行化处理的指令,并尽可能的优化流水线

这里主要介绍第一个优化方向,即通过扩展自定义指令集,降低最终执行指令数量来提升性能。这里面针对 eBPF 指令本身的特点和网络处理的特点做了很多针对性的优化,达到了尽管我主频比 x86 低,但是我指令数少一样性能好的结果,展示了当可以自己定义硬件能力和指令集时优化的独特能力。

  1. 删除零值初始化操作代码。eBPF 代码中会有大量初始化赋零值的操作,这部分 FPGA 可以通过硬件保证,因此初始化赋零值的指令可以被全部删掉。
  2. 使用三操作数指令代替二操作数指令。由于 eBPF 指令集吸收了很多 RISCV 的理念,使用的是二操作数指令,这会导致指令数的膨胀。作者的编译器会将这些指令翻译成对应的三操作数指令来降低指令数。例如 eBPF 中 r4=r2; r4 =14; 两条指令可以翻译成 r4=r2 14这样一条指令。
  3. 删除边界检查。eBPF 指令为了保证在内核执行的安全性,会加入大量数组边界检查的指令,这一部分同样可以通过硬件完成,这部分指令也可以被完全移除。
  4. 增加 6B store/load 指令。eBPF 的 store/load 指令单位为 1B、2B、4B、8B,但是网络处理中需要大量读取的 mac header 是 6B,因此通过扩展 6B 指令可以减少部分 store/load 指令。
  5. 参数化返回。XDP 应用最后通常是将执行的动作保存在 r0 寄存器,然后再退出。例如 r0=1;exit; 这可以优化成一条 exit_drop

测试结果

作者选取了几个在 Linux 代码库中的 XDP example ,一个简单的 XDP 防火墙和 Facebook 开源的 Katran 作为测试应用,对比同样的应用在 FPGA 和 运行在 1.2GHz,2.1Ghz 和 3.7GHz CPU 上的性能对比。作者测试的指标比较多,简单列一些我比较关注的:

  1. 针对指令集的优化,平均下来大概能精简掉 40% 左右的指令数,而 eBPF 在 JIT 到 x86 时通常指令数还会膨胀,因此相对最终的 x86 指令精简幅度会更大
  2. 延迟测试中由于 FPGA 相对 CPU 少了数据传输时间,普遍延迟只有 CPU 的十分之一甚至更低
  3. 在 Linux XDP example 测试中吞吐量大概和 2.1GHz CPU 单核处理能力相当
  4. Firewall 和 Katran 测试中吞吐量在 2.1GHz 和 3.7GHz CPU 单核能力之间

个人想法

  1. 扩展指令集进行优化的方式对应搞软件的人来说还是个很新颖的思路,感觉一些优化方法也可以应用到 eBPF 到 x86 的 JIT 或者其他 ASIC 网卡的设计
  2. 尽管作者用了 XDP 和 FPGA 的双重魔法,想占两边的便宜,但是从性能表现看只是达到了 CPU 运行 XDP 的水平。吸引力其实就下降了不少,因为不用 XDP 单独用 FPGA 方式可以达到数倍的吞吐量。使用 XDP offload 就变成了 CPU 和 FPGA 1 换 1 的操作,效能比就不好看了
  3. 从指令集的优化来看 eBPF 最初的目的还是通用编程,并没有针对网络处理进行优化,那么直接在 eBPF 内扩展这样一套针对网络优化的指令集,在 x86 上是不是会有更好的表现?

0 人点赞