

I/O库的CPU影响

libaio / io_uring(中断):需要32个线程才能达到约1000万IOPS,且CPU已满载。大量时间花在内核和中断处理上。
io_uring(轮询模式):表现更好,需要16个线程接近满带宽。减少了中断,但仍有内核路径开销。
SPDK(用户态旁路):仅需3个线程就能打满1250万IOPS。因为它完全绕过了内核,没有系统调用、没有中断、没有内核栈开销。I/O管理(写提交队列、轮询完成队列)直接在用户态以极低的开销完成。
User-space I/O with SPDK.
基于SPDK的用户态I/O。
Intel’s Storage Performance Development Kit (SPDK) is a set of libraries and tools used for high-performance storage applications.
英特尔® 存储性能开发套件(SPDK)是一套用于构建高性能存储应用的工具和库。
Specifically, the SPDK NVMe driver, which is the user-space driver for NVMe SSDs, is relevant for us.
其中,SPDK NVMe驱动作为NVMe固态硬盘的用户态驱动,与我们讨论的内容直接相关。
As Figure 5d illustrates, to communicate with an NVMe SSD, SPDK directly allocates the NVMe queue pairs (for submission and completion) in the user space.
如图5d所示,为了与NVMe固态硬盘通信,SPDK直接在用户空间内分配NVMe队列对(用于提交和完成)。
Submitting I/O requests is therefore as simple as writing a request into a ring buffer in memory and notifying the SSD that there are new requests available through another write.
因此,提交I/O请求就如同将请求写入内存中的一个环形缓冲区,并通过一次额外的写操作来通知固态硬盘有新的请求待处理。
SPDK does not support interrupt-driven I/O and completions always have to be polled from the NVMe completion queue.
SPDK不支持中断驱动的I/O,其完成通知必须通过轮询NVMe完成队列来获取。
SPDK completely bypasses the operating system kernel, including the block device layer, file systems, and the page cache.
SPDK完全绕过了操作系统内核,包括块设备层、文件系统以及页缓存。
方面 | SPDK | libaio / io_uring |
|---|---|---|
内核参与 | 无(完全旁路) | 有(经过完整内核栈) |
系统调用 | 无 | 有(io_submit / io_uring_enter) |
中断/轮询 | 强制轮询(低延迟) | 可选轮询或中断 |
数据拷贝 | 零拷贝(直接访问硬件队列) | 通常需要用户态-内核态拷贝 |
线程数需求 | 极少(2-3 个) | 多(8-32 个) |
CPU 利用率 | 高效(每 I/O 周期少) | 较低(大量周期花在内核) |
SSD是一个高度并行的设备
SSD的物理结构是并行的
SSD内部有多个通道,每个通道连接着独立的闪存芯片。
这些通道和芯片可以同时工作,就像一条多车道的高速公路,可以同时跑很多辆车。
高并发请求是性能关键
因为SSD内部是并行的,所以单个I/O请求无法利用其全部能力。
要“喂饱”SSD,让它达到最高的吞吐量(IOPS),必须同时向它提交大量的I/O请求,让所有通道和芯片都忙起来
好的,这是对您提供的文档中 2.5 A Tight CPU Budget 章节的精确中英对照翻译。
可用的CPU周期。
第2.1节的实验表明,现代存储硬件能够实现数千万的IOPS。 The experiments in Section 2.1 show that modern storage hardware is capable of achieving tens of millions of IOPS. 【硬件足够好了,不是瓶颈】
管理如此大量的请求成为数据库系统的一项重要任务,并且会占用大量的CPU时间。 Managing that many requests becomes an important task for the database system and can take up a significant amount of CPU time. 【IO不只能用cpu,为什么管理IO请求消耗这么多cpu】
简单回答是: 是的,I/O本身(数据在SSD和内存之间传输)不占用CPU。
但管理这些I/O请求的过程— —包括生成、提交、跟踪、完成通知和错误处理——会消耗大量的CPU时间。
让我们拆解一下为什么“管理I/O”会如此耗费CPU:
文档第2.5节的计算清晰地指出了这个矛盾:
硬件能力:8块NVMe SSD可以提供 1250万次随机4KB读取/秒 (12.5 M IOPS)。
CPU预算:一台64核、2.5 GHz的服务器,每秒总CPU周期为:2.5 GHz × 64 核心 = 1600亿个周期/秒。
每个I/O的CPU周期预算:1600亿周期 / 1250万IOPS ≈ 13,000 周期/IOPS。
这意味着,数据库系统平均只有大约13,000个CPU周期来处理一个I/O请求的“全部工作”。这“全部工作”包括:
查询处理
索引遍历(B树/B+树查找)
并发控制(锁、闩锁)
日志记录
缓冲区管理(查找、替换)
以及最重要的:I/O请求的提交和回收管理
“管理I/O”具体消耗CPU在哪些地方?
当数据库需要发起一个I/O(例如,读取一个不在内存中的页面)时,它需要执行一系列软件操作,这些操作完全在I/O硬件开始工作之前发生,并且会消耗CPU:
1、请求构造与提交:
准备I/O请求描述符(操作类型、地址、长度等)。
将请求放入队列(如 io_uring 的提交队列SQ)。
通过系统调用(如 io_uring_enter)通知内核。每次系统调用都涉及从用户态到内核态的上下文切换,开销巨大。
2、完成事件处理(更耗CPU):
默认(中断模式):每个I/O完成时,硬件会触发一个中断。CPU必须暂停当前工作,保存现场,跳转到中断处理程序,处理完成事件,再恢复现场。在每秒千万级IOPS下,中断风暴会彻底压垮CPU。
轮询模式(如IOPOLL):为了避免中断开销,应用程序必须主动、频繁地检查完成队列(CQ),看看有没有请求完成。这个“不断检查”的循环本身就在持续消耗CPU周期。
【传统高效的中断 方式 让cpu更加忙碌】
1、内核路径开销: 对于 libaio 和 io_uring 等内核接口,请求提交后,还需要经过Linux内核的完整I/O栈:
文件系统层(VFS)
页缓存(如果使用)
块设备层
调度器(如MQ-deadline)
最后才到达NVMe驱动 这些内核层的代码执行、锁竞争、内存分配等,都会消耗CPU时间。图6的实验表明,使用 libaio 和 io_uring(中断模式)时,大部分时间都花在了内核里。
2、数据对比:不同I/O接口的CPU效率
这正是第2.5节图6要说明的关键点:
•
libaio / io_uring(中断):需要32个线程才能达到约1000万IOPS,且CPU已满载。大量时间花在内核和中断处理上。
•
io_uring(轮询模式):表现更好,需要16个线程接近满带宽。减少了中断,但仍有内核路径开销。
•
SPDK(用户态旁路):仅需3个线程就能打满1250万IOPS。因为它完全绕过了内核,没有系统调用、没有中断、没有内核栈开销。I/O管理(写提交队列、轮询完成队列)直接在用户态以极低的开销完成。
在图6所示的微基准测试中,我们测量了使用不同线程数时可达到的吞吐量。
CPU impact of I/O libraries.
In the microbenchmark shown in Figure 6, we measured the throughput achievable with a varying number of threads.
该图显示,使用libaio和io_uring(基于中断)时,无法达到全部带宽。 T he figure shows that with libaio and io_uring (interrupt based), the full bandwidth cannot be reached.
使用32个线程时,最大吞吐量为1000万IOPS,其中大部分时间花费在内核中。 With 32 threads, the maximum throughput is 10 M IOPS, with most of the time being spent in the kernel.
使用轮询模式的io_uring时,结果更好:使用16个线程就可以接近全部吞吐量。 With io_uring in poll mode, the results are better: with 16 threads one can get close to the full throughout.
图6所示的实验是使用自定义的基准测试工具测量的。
fio can be the bottleneck.
The experiments shown in Figure 6 were measured with a custom benchmarking tool.
我们也用标准的I/O基准测试工具fio [2]执行了相同的基准测试,令人惊讶的是,得到了更差的结果。 We also executed the same benchmarks with the standard I/O benchmarking tool fio [2] and, surprisingly, got worse results.
特别是,fio基于中断的I/O实现无法在使用随机4 KB读取时达到全部带宽。 In particular, fio's implementation of interrupt-based I/O cannot achieve the full bandwidth with random 4 KB reads.
[why 中断的I/O 无法占满全部带宽 ]
一个专门的I/O基准测试工具可能成为瓶颈这一事实表明, 在成熟的数据库系统中实现高I/O吞吐量是困难的。
The fact that a specialized I/O benchmarking tool can become a bottleneck indicates that achieving high I/O throughput in full-blown database systems is difficult.
Full-blown :
•
成熟的,功能齐全的,完全发展的
1、NVMe硬件潜力巨大,但难以充分利用
现代NVMe存储设备具有极高的性能潜力(如高IOPS、低延迟)。
然而,即使在微观基准测试(如fio工具测试)中,接近硬件极限也非常困难,而在完整的存储引擎中实现更是重大挑战。
数据传输(数据在SSD和内存之间移动)由DMA(直接内存访问)硬件完成,
确实不占用CPU。但发起、调度、监控和完成I/O请求的软件过程需要CPU参与。
1、请求提交与系统调用开销
每次I/O请求都需要准备请求描述符(操作类型、地址、长度等)。
对于内核接口(如pread、libaio、io_uring),需要系统调用将请求提交给内核。每次系统调用都涉及用户态到内核态的上下文切换,这是昂贵的操作。
文档指出,使用同步I/O(如pread)时,每个页面错误都需要一次上下文切换,线程会被阻塞直到I/O完成。
2、内核I/O栈处理
请求进入内核后,需要经过完整的I/O栈:文件系统层、页缓存、块设备层、I/O调度器,最后才到达NVMe驱动。
这些内核层的代码执行、锁竞争、内存分配都会消耗CPU时间。图6显示,使用libaio和io_uring(中断模式)时,大部分时间都花在内核中。
3、完成事件处理(更关键)
中断模式:每个I/O完成时,硬件触发中断。CPU必须暂停当前工作,执行中断处理程序。在每秒千万级IOPS下,中断风暴会彻底压垮CPU。
轮询模式:为避免中断,应用程序必须主动、频繁地轮询完成队列。这个“不断检查”的循环本身就在持续消耗CPU周期。
4、高并发管理开销
如2.3节所述,SSD需要大量并发I/O请求(约1000-3000个)才能达到饱和性能。
管理成千上万个并发的I/O状态、队列和回调函数需要复杂的软件逻辑,这些都会消耗CPU。
文档中的实验(图6)清晰展示了CPU开销差异:
I/O接口 | 达到满带宽所需线程数 | CPU效率 | 原因 |
|---|---|---|---|
SPDK | 仅需3个线程 | 最高 | 完全绕过内核,无系统调用、无中断、用户态直接轮询NVMe队列 |
io_uring(轮询模式) | 需要16个线程 | 中等 | 减少中断,但仍有内核路径开销 |
libaio / io_uring(中断) | 32个线程也无法满带宽 | 最低 | 系统调用+中断处理开销大 |
SPDK通过完全绕过内核解决了上述大部分开销:
1、无系统调用:直接在用户态操作NVMe硬件队列。
2、无中断:始终轮询完成队列,避免中断处理。
3、零拷贝:数据直接在用户缓冲区和SSD之间传输。
4、极简路径:从应用到硬件的路径最短。
因此,SPDK仅用3个线程就能打满带宽,而其他接口需要16-32个线程,且仍可能无法达到满带宽。
I/O消耗CPU不是因为数据传输,而是因为:
1、系统调用和上下文切换(用户态↔内核态)
2、内核I/O栈的处理开销
3、中断处理或轮询开销
4、高并发I/O状态管理
