现代异步存储访问API探索:libaio、io_uring和SPDK

【摘要】
最近的高性能存储设备暴露了现有软件栈的低效,因而催生了对i/o栈的改进。linux内核的最新api是io_uring。作者提供了第一个针对io_uring的深度研究,并且和libaio、spdk比较,探讨它的下性能和优缺点。根据作者的发现,(1)轮询能极大影响性能(2)只要cpu核足够多,io_uring可以提供和spkd接近的性能(3)在多核cpu和多设备场景下扩展需要仔细的考虑并且需要一个混合方案。最后,作者为存储密集的应用开发者提供了设计指导。
【三种api简介】
1、libaio
传统的同步i/o接口包括read()、write()、pread()、pwrite()等,线程开始i/o操作后立刻进入阻塞状态,直到i/o请求完成。而使用异步i/o接口,如aio,线程把i/o请求发送给内核后可以继续做其他工作,直到内核把i/o请求完成的信号发送给线程。通常,异步i/o接口效率更高,其中的核心系统调用是io_submit(用于提交i/o请求)和io_getevents(用于获得完成的i/o请求)。然而,在每个i/o操作中,libaio要依赖两个系统调用,而且使用中断的方式通知i/o请求的完成,这导致libaio的单个i/o性能并不好,如下图。
2、spdk
spdk是linux的高性能api。它在用户空间映射了pcie寄存器以配置cq和sq,用户通过轮询cq来捕获i/o请求的完成,而不需要中断和系统调用。spdk的缺点是它很复杂,而且相对于libaio适用范围很窄。spdk不支持文件系统,也无法利用内核存储服务,如访问控制、调度、qos和配额管理。
3、io_uring
io_uring中和了上述两类api的优缺点。它在用户空间实现了两个环形数据结构,同时内核可以访问它们,类似于nvme的cq和sq,submission ring存储了用户提交的i/o请求,completion存储了i/o请求的完成结果。用户可以不通过系统调用插入和检索两个环。
io_uring提供的i/o机制有三种,如下图:
默认模式下,用户可以通过io_uring_enter系统调用通知内核新请求已经提交到sq中。用户使用同一个io_uring_enter系统调用等待i/o请求完成。io_uring_enter支持中断模式(a)和轮询模式(b)。当然,因为用户也可以访问cq,所以用户可以自己轮询cq等待i/o请求完成,而不使用任何系统调用。并且,io_uring也可以使用一个内核线程轮询sq,这样在整个i/o操作中不会使用任何系统调用(c)。
【性能测试分析】
实验使用fio生成4kb随机读负载,不使用page cache。 纯读负载能达到更高的iops,而高iops有助于分析不同api的可扩展性趋势和每个i/o操作的开销。 除了io_uring外,其他api均使用默认配置。io_uring的配置如下:
(1)iou:上图(a)的配置(默认的fio参数)
(2)iou+p:上图(b)的配置(fio参数是hipri)
(3)iou+k:不使用系统调用,即使用内核线程轮询i/o的提交,同时应用轮询i/o的完成(fio的参数是sqthread_poll)
环境配置如下表:
p.s. 虽然io_uring里提供了io_uring_enter作为提交i/o请求和捕获完成的i/o请求的统一接口,但fio里面还是分开使用了(即调用了两次io_uring_enter)。
1、理解轮询
作者使用单个fio job、单个nvme驱动和单个cpu,在不同队列深度下,测试三种api(libaio、io_uring和spdk)的kiops,如下图:
每个iops下对应的延迟中位数如下图:
平均每个i/o操作进行的系统调用个数如下图:
① 可知,iou+k的kiops仅仅13,比其他api少一个数量级。因为此时fio线程和内核轮询线程共享一个cpu,减少了fio每秒处理的i/o请求的数量。同时,iou+k的延迟是8ms,比其他api慢1~2个数量级。iou+k的延迟不随kiops的变化而变化,因为此时的延迟取决于cpu资源的竞争,而非排队等待。
当cpu数量增加到2时,iou+k的性能完全恢复了,如下图:
每个kiops对应的延迟中位数为:
此时,iou+k的性能仅次于spdk:最大带宽比spdk小18%,延迟和spdk相当。
② spdk在所有场景下性能最好,也是唯一达到驱动带宽上限的api 。spdk和iou+k的区别在于,iou+k使用两个线程访问同一个变量,会产生原子访问和缓存失效的开销,而spdk使用一个线程,能更加充分的利用资源。
③ 当iops较小时,iou+p的性能和spdk接近,因为队列深度较小时,系统调用的开销还不足以成为性能瓶颈,此时系统态轮询和用户态轮询的性能接近。类似的还有iou和libaio,当队列深度小于16时,二者kiops和延迟都很接近,当队列深度大于16后,iou的kiops和延迟比libaio要好——因为iou使用的系统调用比libaio少,所以可以更加充分的利用cpu资源。
当队列深度小于16时,iou的系统调用比iou+p少,但延迟比iou+p高。原因是,队列较浅时,fio的队列很快会被填满,而当队列满时,fio会等待至少一个请求完成再进行下一步动作。此时,虽然中断比较慢,但iou可能会一次处理较多完成的请求,而轮询则是检测到一个请求完成就退出,从而错失了批量处理多个完成的请求的机会。当队列深度增加时,两个api最后都趋向于每个i/o请求只使用1个系统调用。
iou和libaio的最大区别是,iou多了一个提交队列(sq),这就是它具有批处理能力的原因。
2、不同的cpu-设备比
作者进一步分析了对于iou+k,每个驱动需要多少个cpu以获得最佳性能。此时,作者使用了j=5个fio job测试,每个job运行在不同的驱动上,队列深度为128,c代表cpu的数目,本次实验中,分别测试了c=j,c=j+1,c=j+2和c=j*2的情况。
注意,在iou+k中,内核会为每个job产生一个内核线程以轮询sq获得提交的请求。
实验结果如下图:
可见,除了iou+k,其他api的带宽表现和cpu-设备比无关。而iou+k需要两倍于驱动数的cpu才能达到最好的性能——即每个线程需要一个单独的cpu来轮询。糟糕的是,当cpu数不是最佳时,iou+k的kiops是最低的。这一实验结果揭示了iou+k要实现高性能的隐藏开销,而其他测试忽略了这一点。
3、可扩展性
作者控制job从1到20,以测试不同api的可扩展性。每个job访问不同的驱动,设置cpu数c=2*j(由于硬件限制,c最大可以取到20),队列深度为128。下图是测试结果:
spdk的性能总是最好的,而性能第二好的api取决于job的数量和cpu核的数量。当j不大于10时,iou+k可以给每个内核线程分配一个cpu,性能最好。此后随着j的增大,内核线程和应用线程开始抢夺cpu资源,kiops开始下滑。在j=12时,iou+k和iou、iou+p的kiops交汇,在j=14时,iou+k成为性能最差的api。其他的api随着job的增长,kiops也基本上稳定增长。
iou和iou+p的性能很接近,libaio的带宽也仅仅比iou、iou+p少10%。
【总结与讨论】
1、不同的轮询方式各有特点
lspdk的优势不仅仅体现在用户态轮询、无系统调用开销,还体现在使用单个线程进行轮询。
liou+k的优势在cpu不够时不明显。
liou+p使用系统级轮询,在队列深度较小时可以和spdk相当。
2、io_uring在特定配置下的性能接近spdk
3、性能的可扩展性需要仔细考虑
虽然spdk的性能最好,但需要放弃linux文件的支持。如果需要使用文件系统,且cpu足够多,iou+k是不错的选择(可以达到90% spdk的性能),而若cpu资源不足,可以使用iou+p,当队列深度不深时和spdk的性能接近。
未来可以在更在实际的i/o密集型应用上测试,如数据库。它们都需要文件系统的支持,可能会带来额外的同步开销,可能会覆盖i/o路径上的bottleneck。此外还可以研究更高效的iou+k设计,如不同的应用线程共享一个内核轮询线程,或者二者更好的使用cpu资源等。最后,io_uring支持socket i/o,所以它的性能也可以测试以评估网络应用的表现。


新建有机肥厂实验室仪器配置方案/锦农第六代
mcu线程和进程的区别是什么
复旦微电子携安全芯片系列产品FM123X亮相亚洲充电展
适合新型汽车应用的传感器
iPhone14Pro陷烧屏门事件 苹果状况不断
现代异步存储访问API探索:libaio、io_uring和SPDK
__missing__()的实现原理
全球智能音箱市场的发展形势一片大好
德州仪器推出高效率PWM电源控制器UCC28250
接近真机 iPhone 8最靠谱渲染图:帅爆
聚焦两会 汽车人“两会”专题,看看汽车界的大佬都说了什么
使用电容式传感器实现点式和连续式液位传感
ΔΣ转换器的应用优势和实现高精确度性能
典型晶闸管电路图分享
施耐德电气再获“金钥匙·冠军奖” 甲骨文和红牛车队加强合作关系
网友晒初代未开封iPod产品 价格已涨50倍
真空开关阀的工作原理和构造
如何设计一个获取“Up There”反馈的电路
智能离子风棒联网监控静电消除器的工作原理和特点
超声波塑料焊接的熔接面设计