本文翻译自 io_uring and Grand Central Dispatch std.Io implementations landed,原载于 Hacker News。
核心更新
随着 Zig 0.16.0 发布周期接近尾声,Jacob 完成了一项重要工作:让 std.Io.Evented 赶上了最新的 API 变更。现在 Zig 标准库正式支持两种新的异步 I/O 实现:
- io_uring 实现 - Linux 的高性能异步 I/O 接口
- Grand Central Dispatch 实现 - macOS/iOS 的并发调度框架
这两种实现都基于用户态栈切换(userspace stack switching)技术,你可能听过它的其他名字:「fibers」、「stackful coroutines」(有栈协程)或「green threads」(绿色线程)。
如何使用
现在你可以在自己的项目中尝试这些新特性。不过先提醒一下,它们目前还处于实验性阶段,在正式生产使用前还有一些工作要做:
- 更好的错误处理
- 移除调试日志
- 诊断使用
IoMode.evented时编译器性能下降的问题 - 少数函数尚未实现
- 需要更多测试覆盖
- 需要一个内置函数来告诉你给定函数的最大栈大小(这对于关闭 overcommit 时使用这些实现非常重要)
代码示例
让我们看看实际代码。首先是使用 std.Io.Threaded 的传统线程版本:
const std = @import("std");
pub fn main(init: std.process.Init.Minimal) !void {
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
const gpa = debug_allocator.allocator();
var threaded: std.Io.Threaded = .init(gpa, .{
.argv0 = .init(init.args),
.environ = init.environ,
});
defer threaded.deinit();
const io = threaded.io();
return app(io);
}
fn app(io: std.Io) !void {
try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
}
使用 strace 追踪系统调用:
$ strace ./hello_threaded
execve("./hello_threaded", ["./hello_threaded"], 0x7ffc1da88b20 /* 98 vars */) = 0
mmap(NULL, 262207, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f583f338000
...
writev(1, [{iov_base="Hello, World!\n", iov_len=14}], 1Hello, World!
) = 14
...
exit_group(0) = ?
可以看到,传统版本使用的是标准的 writev 系统调用。
切换到 io_uring
重点来了!只需要改几行代码,就可以切换到 io_uring:
const std = @import("std");
pub fn main(init: std.process.Init.Minimal) !void {
var debug_allocator: std.heap.DebugAllocator(.{}) = .init;
const gpa = debug_allocator.allocator();
var evented: std.Io.Evented = undefined;
try evented.init(gpa, .{
.argv0 = .init(init.args),
.environ = init.environ,
.backing_allocator_needs_mutex = false,
});
defer evented.deinit();
const io = evented.io();
return app(io);
}
fn app(io: std.Io) !void {
try std.Io.File.stdout().writeStreamingAll(io, "Hello, World!\n");
}
注意到 app 函数完全相同吗?这就是 Zig I/O 抽象的威力。
再看看 io_uring 版本的系统调用:
execve("./hello_evented", ["./hello_evented"], 0x7fff368894f0 /* 98 vars */) = 0
...
io_uring_setup(64, {...}) = 3
mmap(NULL, 2368, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_POPULATE, 3, 0) = 0x7f70a4ba0000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_POPULATE, 3, 0x10000000) = 0x7f70a4b9f000
io_uring_enter(3, 1, 1, IORING_ENTER_GETEVENTS, NULL, 8Hello, World!
) = 1
io_uring_enter(3, 1, 1, IORING_ENTER_GETEVENTS, NULL, 8) = 1
...
exit_group(0) = ?
现在使用的是 io_uring_setup 和 io_uring_enter,这是 Linux 的高性能异步 I/O 接口。
当前状态
Zig 编译器本身已经可以使用 std.Io.Evented 正常工作(无论是 io_uring 还是 GCD),但如前所述,目前存在性能下降的问题,还需要进一步调查和优化。
个人见解
这次更新体现了 Zig 语言设计的一个核心理念:抽象不应该有运行时成本。你写的是同一份业务代码,但底层可以根据平台选择最优的 I/O 实现:
- Linux 上用 io_uring 获得极致性能
- macOS/iOS 上用 GCD 充分利用系统优化
- 其他平台回退到线程池
这种「一次编写,处处优化」的能力,对于需要跨平台高性能的项目来说非常有价值。io_uring 虽然强大,但直接使用 API 相当复杂,Zig 把它封装成标准库的一部分,大大降低了使用门槛。
期待 Zig 0.16.0 正式发布时这些特性能更加成熟稳定!
Happy hacking!
关键要点:
- Zig 0.16 新增 io_uring 和 GCD 两种异步 I/O 实现
- 基于用户态栈切换(fiber/coroutine)技术
- 业务代码无需修改即可切换底层 I/O 实现
- 目前处于实验阶段,存在一些待优化项
- 体现了 Zig「零成本抽象」的设计哲学