bigywater.tech
Rust · 132

BufReader 与缓冲读取

BufReader 为什么能让读文件快几个数量级?默认缓冲多大?lines() 为什么比 read_line 慢?什么时候不需要它?这篇用 read 调用计数和 20 MB 文件的计时回答。

100001 对 14
读 10 万字节:无缓冲与 BufReader 的底层 read 调用次数
34.2 对 19.3 ms
lines() 与复用 String 的 read_line 读 100 万行的耗时
8192
BufReader 的默认缓冲区容量(字节)

read 调用次数

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    无缓冲 File + bytes():文件 100000 字节,逐字节读,底层 read 调用 100001 次(每个字节一次)
    BufReader::new(默认容量 8192 字节) + bytes():读了同样 100000 字节,底层 read 调用 14 次
      with_capacity(   64):底层 read 调用  1564 次(约 文件大小 / 容量 = 1562)
      with_capacity( 1024):底层 read 调用    99 次(约 文件大小 / 容量 = 98)
      with_capacity(65536):底层 read 调用     3 次(约 文件大小 / 容量 = 2)

文件读写基础见std::fs 文件读写;写入侧见BufWriter 与批量写入。

耗时对比

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    文件 20000000 字节、1000000 行
    read_to_string 一次读完再 lines():         9.6 ms  (行数 1000000)
    BufReader::lines()(每行分配一个 String):    34.2 ms  (行数 1000000)
    BufReader + read_line 复用同一个 String:      19.3 ms  (行数 1000000)
    同上,缓冲区 1 MiB:                      18.2 ms  (行数 1000000)
    无缓冲 File 逐字节读(只读前 20 万字节)    73.2 ms  (只读了文件的 1.0%,按字节数折算整个文件约 7315 ms)

文件在系统缓存中(不是冷读),耗时是本机 release 实测的最小值;无缓冲整文件耗时是按前 20 万字节折算的。

BufRead 的底层接口

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    fill_buf(缓冲区容量 8)看到 "alpha\nbe"(8 字节),还没有消费
    consume(3) 之后 buffer() = "ha\nbe"
    read_until(\n) 得到 "ha\n"(包含分隔符)
    lines() 的结果:["alpha", "beta", "gamma"](\n 与 \r\n 都被去掉,最后一行没有换行也算一行)

不需要 BufReader 的场景

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    File::read_to_string 0.34 ms   BufReader::read_to_string 0.32 ms(文件 4000000 字节)

分步演示

BufReader
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。read 调用次数是在 Rust 层包装 File 统计的,不等同于内核里的系统调用统计(没有使用 dtruss/strace)
    • 没有验证:冷缓存(磁盘)读取、mmap 读取(见内存映射一篇)、大文件上缓冲区大小的最佳值、BufReader 与 Stdin::lock 的组合、异步文件读取
    京ICP备2026067502号-1京公网安备11010502063487号