0

jasyncfio:Java中基于linux io_uring的高性能IO操作库

 1 year ago
source link: https://www.jdon.com/67081.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

jasyncfio:Java中基于linux io_uring的高性能IO操作库

io_uring — 是 Linux 内核中相对较新的 API,在版本 5.1 中引入。io_uring 的构建理念是为文件和网络套接字提供高性能异步输入/输出 (IO)。

io_uring 基于内核和用户空间内存之间共享的两个队列,即提交队列 ( sq) 和完成队列 ( cq)。用户向提交队列写入操作请求,内核将操作结果写入完成队列。然后用户需要读取并处理该结果。
这种方式允许程序通过单个系统调用向内核发出多个IO操作的请求,相应地,内核可以通过适当的队列返回多个IO操作的结果。

可以在 API 作者 Jens Axboe - Efficient IO with io_uring撰写的优秀文章中找到有关 io_uring 的更多详细信息。

如何使用 io_uring 的 Java jasyncfio  API
首先,需要初始化EventExecutor,它封装了事件循环以及与 io_uring 相关的所有内容:

EventExecutor eventExecutor = EventExecutor.initDefault();

EventExecutor ​​​的initDefault()方法使用合理的默认值进行初始化,并在内部创建单个 io_uring 实例。

EventExecutor eventExecutor = EventExecutor.builder()
                .entries(128)
                .ioRingSetupIoPoll() // this parameter creates two io_urings, which I wrote about earlier
                .ioRingSetupSqPoll(1000)
                .build();

创建后EventExecutor,一切都已设置完毕并准备好处理文件:

CompletableFuture<AsyncFile> asyncFile = AsyncFile.open(filePath, eventExecutor, OpenOption.READ_WRITE);
AsyncFile file = asyncFile.get();
ByteBuffer buffer = ByteBuffer.allocateDirect(4096);
CompletableFuture<Integer> readCompletableFuture = file.read(buffer);

请注意,只允许使用DirectByteBuffer。这是一个特意的决定,这也是它与标准Java库不同的地方,后者接受任何类型的缓冲区。

在对标准Java库的调用中,如果没有传递DirectByteBuffer,无论如何都会分配一个DirectByteBuffer。读取将被执行到这个缓冲区,然后数据将被复制到用户提供的缓冲区。这造成了隐藏的开销成本,这对于一个高性能的IO库来说是不可接受的。

基准测试
使用fio基准测试的结果作为参考值。在fio中,有一个名为one-core-peak.sh的方便脚本,用于io_uring,它可以优化配置io_uring,以实现当前环境下的最大IOPS数。

一台装有AMD Ryzen 7 4800H CPU、32GB内存和三星SSD 970 EVO Plus 500GB的笔记本电脑。它正在运行Ubuntu 22.04,Linux内核版本为6.2.14-060214-generic。

结果(启用轮询队列):

io_uring: Running taskset -c 0,1 t/io_uring -b512 -d128 -c32 -s32 -p1 -F1 -B1 -n2  /dev/nvme0n1

IOPS=329.81K, BW=161MiB/s, IOS/call=31/31
IOPS=329.51K, BW=160MiB/s, IOS/call=31/32
IOPS=330.12K, BW=161MiB/s, IOS/call=31/31

现在让我们来看看jasyncfio基准测试的结果。

用Java写了一个程序,请注意,目前不支持文件描述符的注册和io_uring文件描述符。

让我们用最接近fio的配置来运行jasyncfio基准测试:

java -jar benchmark/build/libs/benchmark-1.0-SNAPSHOT.jar -b512 -d128 -c32 -s32 -p=true -O=true -w2 /dev/nvme0n1

IOPS=288736, BW=140MiB/s, IOS/call=32/31
IOPS=286624, BW=139MiB/s, IOS/call=32/31
IOPS=285728, BW=139MiB/s, IOS/call=32/31

差异约为-15%。

问题是,我还没有找到一个最佳的方法来实现在使用IORING_SETUP_IOPOLL标志时与io_uring一起工作。

如果我们从基准配置中去掉poll(-p)标志,那么结果将如下:

java -jar benchmark/build/libs/benchmark-1.0-SNAPSHOT.jar -b512 -d128 -c32 -s32 -p=false -O=true -w2 /dev/nvme0n1

IOPS=320672, BW=156MiB/s, IOS/call=32/31
IOPS=316672, BW=154MiB/s, IOS/call=32/32
IOPS=315232, BW=153MiB/s, IOS/call=32/32

操作SSD的IO操作每秒31.5万次!

对fio运行同样的配置:

taskset -c 0,1 t/io_uring -b512 -d128 -c32 -s32 -p0 -F0 -B0 -n2  /dev/nvme0n1

IOPS=328.00K, BW=160MiB/s, IOS/call=32/32
IOPS=328.80K, BW=160MiB/s, IOS/call=32/31
IOPS=329.47K, BW=160MiB/s, IOS/call=32/32

现在,Java和C之间的差异不到5%!

总结
io_uring是一个惊人的IO API,它允许从硬件的IO子系统中提取几乎最大的性能,而CPU和内存的使用量却最小。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK