io_uring 没有 readahead 会怎样?

Io_uring Without Readahead

我在 Turso 数据库项目中尝试为 io_uring 后端实现 readahead 功能,结果发现差异巨大。默认使用 O_DIRECT 的 io_uring 会禁用内核预读,导致每次只提交一个 SQE,无法利用块层的请求合并,性能反而不如 syscall。开启应用层 readahead 后,虽然多提交了请求,但通过并发预读让设备接收的请求数大幅减少,平均请求尺寸显著提升。此外,我还对比了 sqpoll 线程与默认模式,发现 O_DIRECT 模式下数据直接写入用户缓冲区,绕过了内核拷贝,导致 CPU 缓存命中率下降。这篇文章深入剖析了这些底层 I/O 细节,揭示了不同配置下的性能代价。

O_DIRECT 跳过了拷贝步骤,但数据最终仍需要到达 CPU,在缓冲读取中这发生在拷贝期间,而在 O_DIRECT 模式下则表现为查询过程中的缓存未命中。
  1. ComputerGuru

    这篇文章挺有意思,但让我有点恐慌。基准测试(无论是用 TPC 还是其他方式)绝不是确定此处正确方案的方法;基准测试严格来说只适用于那些实际上“独占”其运行硬件的数据库(通常是 RDBMS)。如果嵌入式数据库作为纯 CRUD 应用的后端运行,且几乎不进行任何服务端渲染、解析、验证等操作,本质上只是一个异步的 http 到 SQLite 的接口,那么它可能会以这种方式使用。但更有可能的是,嵌入式数据库会被部署在承担多种用途的机器上(甚至不一定是服务器),需要在可用资源的限制内以及相对意义上表现最佳,这必然需要在 CPU 或内存使用等“价值”方面做出权衡,有时甚至牺牲性能。

    这不仅仅关乎基准测试,任何时候当你从内核手中接管缓存所有权时,这都是一个必须考虑的关键点。内核是堆栈中唯一能够洞察全局状态、并在压力下可信地释放内存以确保所有组件和谐共存的组件。这不仅仅局限于数据库或内存,例如 FreeBSD 就遇到过不少问题,其根源在于 ZFS 拥有独立于内核之外的缓存(尽管两者之间有紧密的集成,并且存在各种机制来解决病态情况 […])

  2. marginalia_nu

    如果你要在用户空间做连续预读(readahead),为什么不直接用 preadv 呢?它会将你的预读限制在下一个常驻页之前,但至少在我对 Marginalia 索引的实验中,只要能用单次 preadv 调用完成全部读取,preadv 在所有情况下都胜过 io_uring。

  3. amluto

    我很好奇为什么选择是在系统调用和 specifically io_uring 配合 O_DIRECT 之间。据我所知,Turso 和 SQLite 一样,支持多个进程访问同一个数据库,我预计缓冲在某些工作负载中会带来巨大收益。不用 direct 的 io_uring 有什么问题吗?还有一个折中方案是 RWF_DONTCACHE。

  4. ErroneousBosh

    这篇文章在我的信息流里滑过,每次我读都看成是“Io_uring without Radiohead”(没有电台司令的 io_uring),我是说你可以这么做,但有什么意义呢?

  5. weatherlite

    Io_urine 是用黄色线程写的吗?

同日更多故事

2026-09-01