宣布 tokio-uring:为 Tokio 提供 io-uring 支持

2021年7月19日

今天,我们发布了 “tokio-uring” crate 的第一个版本,为 Linux 上的 io-uring 系统 API 提供支持。此版本提供异步文件操作,我们将在后续版本中添加对更多操作的支持。

要使用 tokio-uring,首先,添加对 crate 的依赖

tokio-uring = "0.1.0"

然后,启动一个 tokio-uring 运行时并从文件中读取

use tokio_uring::fs::File;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    tokio_uring::start(async {
        // Open a file
        let file = File::open("hello.txt").await?;

        let buf = vec![0; 4096];
        // Read some data, the buffer is passed by ownership and
        // submitted to the kernel. When the operation completes,
        // we get the buffer back.
        let (res, buf) = file.read_at(buf, 0).await;
        let n = res?;

        // Display the contents
        println!("{:?}", &buf[..n]);

        Ok(())
    })
}

tokio-uring 运行时在底层使用 Tokio 运行时,因此它与 Tokio 类型和库(例如 hypertonic)兼容。这是与上面相同的示例,但是我们不是写入 STDOUT,而是写入 Tokio TCP 套接字。

use tokio::io::AsyncWriteExt;
use tokio::net::TcpListener;
use tokio_uring::fs::File;

fn main() {
    tokio_uring::start(async {
        // Start a TCP listener
        let listener = TcpListener::bind("0.0.0.0:8080").await.unwrap();

        // Accept new sockets
        loop {
            let (mut socket, _) = listener.accept().await.unwrap();

            // Spawn a task to send the file back to the socket
            tokio_uring::spawn(async move {
                // Open the file without blocking
                let file = File::open("hello.txt").await.unwrap();
                let mut buf = vec![0; 16 * 1_024];

                // Track the current position in the file;
                let mut pos = 0;

                loop {
                    // Read a chunk
                    let (res, b) = file.read_at(buf, pos).await;
                    let n = res.unwrap();

                    if n == 0 {
                        break;
                    }

                    socket.write_all(&b[..n]).await.unwrap();
                    pos += n as u64;

                    buf = b;
                }
            });
        }
    });
}

所有 tokio-uring 操作都是真正的异步,这与 tokio::fs 提供的 API 不同,后者在线程池上运行。从线程池使用同步文件系统操作会增加显著的开销。使用 io-uring,我们可以从同一个线程异步执行网络和文件系统操作。但是,io-uring 的功能远不止于此。

Tokio 当前的 Linux 实现使用非阻塞系统调用和 epoll 进行事件通知。使用 epoll,一个经过调优的 TCP 代理将在用户空间之外花费 70% 到 80% 的 CPU 周期,包括花费在执行系统调用和在内核与用户空间之间复制数据的周期。Io-uring 通过消除大多数系统调用来减少开销,并且对于某些操作,提前映射用于字节缓冲区的内存区域。早期比较 io-uring 和 epoll 的基准测试很有希望;用 C 实现的 TCP 回显客户端和服务器显示出高达 60% 的改进

最初的 tokio-uring 版本提供了一组适度的 API,但我们计划在未来的版本中添加对 io-uring 所有功能的支持。请参阅 设计文档 以了解我们的发展方向。

因此,请尝试一下这个 crate,并随时 提问报告问题

此外,我们要感谢所有一路以来提供帮助的人,特别是 Glauber Costa(Glommio 作者),他耐心地回答了我的许多问题,withoutboats,感谢他的初步探索 (Ringbahn) 并花时间与我讨论设计问题,以及 quininer,感谢他在纯 Rust io-uring bindings 方面所做的出色工作。