bigywater.tech
Rust · 133

BufWriter 与批量写入

写文件为什么要 BufWriter?一个 writeln! 到底触发几次系统调用?stdout 默认是怎么缓冲的?丢数据的场景有哪些?这篇用写调用计数和 100 万行计时回答。

511110 对 147
写 10 万行:无缓冲与 BufWriter 的底层 write 调用次数
约 288×
100 万行:无缓冲对 BufWriter 的耗时倍数(无缓冲为折算值)
丢 3808 字节
process::exit 没有 flush 时丢失的数据量

底层 write 调用次数

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    无缓冲 File:writeln! 10 万次 → 底层 write 调用 511110 次,共 1200000 字节
    BufWriter(默认容量 8192):底层 write 调用 147 次,共 1200000 字节
    LineWriter(每遇到换行就刷新):底层 write 调用 100000 次
      BufWriter::with_capacity(     64):底层 write 调用  19902 次
      BufWriter::with_capacity(   1024):底层 write 调用   1177 次
      BufWriter::with_capacity(1048576):底层 write 调用      2 次

读取侧见BufReader 与缓冲读取;文件基础见std::fs 文件读写。write 调用数是在 Rust 层统计的,不是内核系统调用计数。

耗时

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    无缓冲 File(10 万行实测折算到 100 万行):    8898.4 ms
    BufWriter 默认容量:                           30.9 ms
    BufWriter 1 MiB 容量:                         28.0 ms
    先拼成 String 再一次 fs::write:                25.5 ms

文件在系统缓存里(没有 fsync),耗时是本机 release 实测的最小值;无缓冲项为 10 万行折算到 100 万行。

stdout

实测 · 本机执行输出
  编译:成功
  println    写 20 万行到 /dev/null:  184.0 ms
  locked     写 20 万行到 /dev/null:  183.7 ms
  bufwriter  写 20 万行到 /dev/null:   11.9 ms

什么时候会丢数据

实测 · 本机执行输出
  编译:成功
  drop             写入 1000 行(共 12000 字节)后,文件大小 12000 字节
  flush_then_exit  写入 1000 行(共 12000 字节)后,文件大小 12000 字节
  exit             写入 1000 行(共 12000 字节)后,文件大小 8192 字节
  forget           写入 1000 行(共 12000 字节)后,文件大小 8192 字节

drop 的规则见Drop Trait 与资源释放;文件描述符的释放见文件描述符与资源释放。

分步演示

BufWriter
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动
    • 没有验证:fsync 后的真正落盘、BufWriter 在写入出错时 drop 的行为(错误被忽略)、into_inner 与 IntoInnerError、stdout 连接到终端时的行缓冲行为、异步写入、写入管道对端慢时的阻塞
    京ICP备2026067502号-1京公网安备11010502063487号