去年帮一个做网关中间件的团队把 epoll 替换成 io_uring,折腾了两周,压测跑出来的数字让所有人沉默了:QPS 从 32 万涨到 34 万,提升 6.3%——这还是在已经深度优化过 epoll 的代码路径上,换了个异步框架写了几千行代码,就换来这么点提升,性价比低得可笑。团队里有人直接说"io_uring 就是个学术玩具,实际收益根本不值得迁移成本",我当时没反驳,因为从数据看他说得确实有道理。
但三个月后我在看 TigerBeetle 的设计文档时,发现了一个让我后背发凉的数字:他们用 io_uring 实现的存储引擎,和传统preadv+io_submit方案对比,单核吞吐量提升了 2.3 倍。同样是 io_uring,为什么差距这么大?我把 TigerBeetle 的 io_uring 初始化代码拉出来一行一行看,发现他们用了IORING_REGISTER_FILES、IORING_REGISTER_BUFFERS、IOSQE_IO_LINK、IORING_SETUP_SQPOLL——而我们那个网关项目,一个都没用。我们只是把 io_uring 当成了一个"API 形状不同的 epoll",把epoll_ctl换成io_uring_prep_poll_add,把epoll_wait换成io_uring_peek_cqe,底下走的还是标准 fd 查找、标准内存拷贝、标准系统调用路径——换句话说,我们避开了 io_uring 真正