SPDK
SPDK(Storage Performance Development Kit)是一套用户态存储开发工具。它把 NVMe 队列、DMA 缓冲区和完成处理放到应用进程里,常用轮询(polling)、每核队列和批量提交减少系统调用、上下文切换与通用内核栈开销。
它解决什么问题
高速 NVMe SSD 的单次访问只需几十微秒,多个设备还能提供很高的并行度。此时,传统路径中的 VFS、文件系统、块层、线程睡眠与唤醒、虚拟化边界,都可能与设备本身一样昂贵。SPDK 让应用直接管理设备队列,因此适合以下场景:
- 应用能长期占用若干 CPU 核,并需要稳定的低延迟或高 IOPS;
- 数据布局简单,或者系统愿意自己实现分配、校验、复制和恢复;
- 请求可以批量发出,设备并行度足以摊薄轮询和 doorbell 成本;
- 设备由单个服务或明确划分的线程、虚拟机、租户负责。
SPDK 本身不是文件系统,也不自动提供 POSIX 接口、页缓存、崩溃一致性、故障恢复或多租户隔离。绕过内核只是缩短数据路径;上层仍要决定数据放在哪里、何时算持久化、设备失败后如何恢复,以及多个任务怎样公平共享 CPU 和 SSD。
关键机制
- 用户态 NVMe 驱动:应用直接向提交队列写命令,并读取完成队列,避免每次 I/O 都经过系统调用和内核块层。
- 轮询完成:线程持续查看完成队列,省去中断和睡眠、唤醒开销。代价是即使负载很低,CPU 核也可能一直忙。
- 每核所有权:常见设计让一个线程或一个核独占队列和状态,减少锁与跨核通信,但不容易与普通计算任务混部。
- 批量与并行提交:一次提交多个相互独立的请求,并尽量少写 doorbell,才能充分利用多盘或深队列。
- 零拷贝或少拷贝数据路径:设备通过 DMA 直接访问注册缓冲区。若上层仍需页缓存、格式转换或隔离缓冲区,复制成本仍会回来。
从论文中得到的共同观察
1. 最大收益来自缩短软件路径,不一定来自轮询本身
Aeolia-SOSP25 把用户态路径和完成方式拆开研究:在主动检查调度策略下,优化后的中断式 4 KB 读取延迟为 6.3 微秒,SPDK 轮询为 5.4 微秒;512 B 小请求时差距可扩大到 18.2%。这说明 SPDK 的优势不能简单写成“轮询一定比中断快”:绕过通用内核路径是主要来源之一,完成方式还取决于请求大小、共享方式和调度器。
DPAS-FAST26 与 UnICom-FAST26 从内核路径给出互补证据。DPAS 表明 polling、hybrid polling 与 interrupt 的最优点会随 CPU 争用和设备负载变化;UnICom 则发现同步 4 KB O_DIRECT 读取中,睡眠、唤醒和调度约占三分之一延迟,并用集中完成线程减少它。两篇都没有直接改造 SPDK,因此它们支持的是“完成策略应随环境变化”,不是“某种内核方案已经全面胜过 SPDK”。
2. 轮询核是实际成本,不是免费的实现细节
RISTRETTO-FAST26 记录了阿里云 ESPRESSO 的生产经验:SPDK 用户态栈把软件开销降低 82.35%,12 块 Gen3 SSD 达到 38.4 GB/s、5.76M IOPS;但 12 块盘需要 6 个专用核,专核的 P99 利用率少于 60%,而虚拟机完成通知仍会触发 VM exit。后续系统因而把数据面逐步迁到 ASIC 与 SoC,SPDK 则保留为板上可编程存储模块。
Sandman-SOSP25 直接处理能耗问题:PCIe 5.0 全闪存服务器采用 SPDK busy polling 时,轻载 CPU 功率仍可达空闲状态的 1.82 倍。Sandman 在 SPDK 上增加浅睡眠、协同唤醒和基于 NIC 队列的突发检测;论文报告性能与 SPDK 相差不超过 5%,同时最高降低 39.38% 功耗和 33.36% 能耗。证据来自 NVMe-oF、200 Gb/s RDMA 和云块存储 trace,不能直接外推到没有 NIC 的本地盘路径。
3. 请求形态决定 SPDK 能不能真正吃满设备
Helmsman-OSDI26 的关键不是单纯换一个 I/O 库,而是先把 ANNS 查询变成许多互不依赖、可批量提交的 cluster 读取,再用 SPDK 直接驱动 12 块裸 Gen5 NVMe。每个 posting list 连续放置,一批命令对每块盘只写一次 doorbell。这种设计在论文的大数据、大 top-k 场景中比现有 DRAM–SSD 系统高 2–16 倍吞吐;若查询仍是逐跳依赖的图搜索,SPDK 也无法消除串行链。
CoPilotIO-OSDI26 给出另一种限制:GPU 可以直接提交 NVMe 命令,但让 GPU 自己持续轮询完成队列会占用 warp、SM 周期和显存带宽。CoPilotIO 保留 GPU 提交,把完成队列放到 CPU 内存,由 16 个 CPU 线程通过类似 SPDK 的用户态路径轮询。论文发现大于 16 KB 的请求可跑满一块 SSD;小请求或多盘总带宽仍可能需要很多 CPU 核。这再次说明,队列位置、完成者和请求粒度必须一起设计。
4. 全面旁路和选择性旁路是两种不同方案
Oxbow-OSDI26 没有完全舍弃内核。读取仍利用 VFS、页缓存和预读,持久化写入才把脏页复制到 SPDK 缓冲区,并从用户态提交。这样保留了应用透明性和成熟读取优化,但付出两次额外复制。它说明系统可以只旁路最贵的一段路径,而不是在“全部走内核”和“全部用 SPDK”之间二选一。
uCache-FAST26 也采用混合方式:文件打开和块地址查询交给 Ext4,数据路径由借鉴 SPDK 的 NVMe uStore 处理。随机读微基准中,uStore 平均比 SPDK 多 3.5% 开销,但原型只支持预分配、非稀疏且打开期间布局不变的文件。WSBuffer-FAST26 则选择保留 Linux buffered I/O 接口,重构写缓存而非把应用迁到用户态;它提醒我们,兼容性、页缓存语义和零改码有时比绝对峰值更重要。
5. 虚拟化、网络和缓存仍可能成为新的瓶颈
RosenBridge-FAST26 发现本地快盘虚拟化时,大量时间仍花在 guest、QEMU/KVM 和 host 软件路径,并指出 SPDK vhost-user 是重要的旁路方向;但论文没有与 SPDK vhost-user 做正面对比,因此不能据此判断 RosenBridge 和 SPDK 谁更快。
Sepia-OSDI26 在 SPDK NVMe-over-TCP 应用上优化 DDIO 的 LLC 工作集,64 KB 和 128 KB 读取带宽最高提高 26.7% 和 51.1%。不过存储后端是 NULL block device,结果证明的是网卡 DMA 与 LLC 管理会限制 SPDK 网络路径,不代表真实 SSD 介质上的同等增益。
设计取舍
| 选择 | 得到什么 | 付出什么 | 论文证据边界 |
|---|---|---|---|
| busy polling | 很短且稳定的完成路径 | 持续占核、能耗高、难与计算混部 | Sandman-SOSP25、Aeolia-SOSP25 |
| 每核队列与裸设备 | 少锁、并行度高 | 设备共享、空间管理与恢复交给应用 | Helmsman-OSDI26、RISTRETTO-FAST26 |
| 完全绕过文件系统 | 避开 VFS、页缓存和块层 | 失去 POSIX、预读、成熟元数据与一致性机制 | Helmsman-OSDI26、uCache-FAST26 |
| 选择性旁路 | 保留成熟内核功能,只加速热点路径 | 跨路径协调和额外复制更复杂 | Oxbow-OSDI26、uCache-FAST26 |
| CPU 代理完成 | 释放 GPU 计算与显存资源 | 小 I/O 或多盘时可能消耗大量 CPU 核 | CoPilotIO-OSDI26 |
| 在 DPU/SoC 上运行 SPDK | 释放 host CPU,并保留软件可编程性 | 增加硬件成本、跨组件调试与故障面 | RISTRETTO-FAST26 |
何时不应直接选择 SPDK
- 负载很低或高度突发,无法接受轮询核长期耗电;
- 应用依赖通用 POSIX、动态文件布局、共享页缓存或复杂文件系统语义;
- 多租户需要内核已有的公平调度、权限检查和资源记账,但上层还没有替代机制;
- 单个请求之间有很长的依赖链,无法形成足够队列深度;
- 团队没有能力长期维护设备分配、校验、崩溃恢复、升级和可观测性。
这并不意味着必须回到未经优化的内核路径。Aeolia、DPAS、UnICom、Oxbow 与 uCache 展示了中间路线:用户态中断、自适应完成、集中完成线程,以及控制面走文件系统而数据面选择性旁路。
仍未解决的问题
- 跨任务共享:如何在不重新引入长内核路径的前提下,让多个任务公平共享 polling 核、队列和 SSD?
- 自适应完成:什么时候轮询、什么时候睡眠或中断,能否同时约束 P99 延迟、CPU 使用和能耗?
- 故障语义:应用直接管理裸设备后,怎样提供可验证的崩溃一致性、校验、重建和在线升级?
- 端到端拓扑:CPU、GPU、DPU、NIC、SSD 与 NUMA 拓扑共同决定性能;只优化 NVMe API 往往会把瓶颈移到 PCIe、LLC 或完成通知。
- 可比成本:论文常把 SPDK 当峰值基线,却不统一计算轮询核、能耗、运维和备用容量。跨系统比较应同时报告这些成本。
相关论文
- Aeolia-SOSP25:把用户态路径与轮询拆开,探索用户态中断和协同调度。
- CoPilotIO-OSDI26:CPU 代理 GPU 发起 I/O 的完成轮询。
- DPAS-FAST26:研究内核 NVMe 完成方式随负载与 CPU 争用的切换。
- Helmsman-OSDI26:用批量、无依赖读取和多盘 SPDK 支撑大规模 ANNS。
- Oxbow-OSDI26:读取保留内核能力,写入选择性进入用户态路径。
- RISTRETTO-FAST26:记录 SPDK 从 host 到 ASIC、SoC 协同架构的生产演进。
- RosenBridge-FAST26:研究虚拟化本地盘的软件路径,并暴露缺少 SPDK vhost-user 对比的证据空白。
- Sandman-SOSP25:减少 SPDK 轮询在突发负载下的能耗。
- Sepia-OSDI26:控制 DDIO 工作集,改善 SPDK NVMe-over-TCP 的 CPU 与缓存效率。
- UnICom-FAST26:以内核集中完成线程替代分散睡眠、唤醒,作为用户态轮询的另一条路线。
- WSBuffer-FAST26:保留 buffered I/O 接口,说明高性能存储不一定要求应用全面旁路内核。
- uCache-FAST26:在 unikernel 中组合文件系统控制面与类似 SPDK 的数据面。