Go文件I/O基准测试必须用func BenchmarkXxx(b *testing.B)函数并加-bench参数运行;需明确测延迟还是吞吐,合理选用os.O_SYNC/O_DSYNC、缓冲区大小及f.Sync()时机,并清理系统缓存确保结果可靠。

用 testing.B 做文件 I/O 基准测试,不是跑 go test -v 就完事了
Go 的基准测试必须用 testing.B 类型的函数,命名以 Benchmark 开头,且需显式启用基准模式。直接运行 go test 不会执行它们;得加 -bench 参数,比如 go test -bench=.。漏掉这个参数,函数压根不跑,你还以为代码没生效。
常见错误现象:写好了 BenchmarkWriteSmallFile,执行 go test 输出全是 ok,但没任何耗时数据——八成是忘了加 -bench。
- 基准函数签名必须是
func BenchmarkXxx(b *testing.B) - 循环体必须套在
b.ResetTimer()和b.ReportAllocs()之间(若关心内存) - 别在
b.N循环外创建待测文件或缓存,否则测的是初始化开销 - 每次迭代应真实触发 I/O:
os.WriteFile、io.Copy、bufio.Writer.Flush都要显式调用
os.O_SYNC 和 os.O_DSYNC 会让 Write 变慢十倍以上
默认 os.OpenFile(..., os.O_WRONLY|os.O_CREATE, 0644) 是异步写入,系统缓存接管后就返回,不代表数据已落盘。想测“真实持久化性能”,必须加 os.O_SYNC(同步元数据 + 数据)或 os.O_DSYNC(仅同步数据)。但代价巨大:在普通 SATA 盘上,小块写可能从 0.1ms 跳到 10ms+。
-
os.O_SYNC确保 write 返回前,数据和 inode 时间戳都刷进磁盘 -
os.O_DSYNC只保证数据落盘,不等时间戳,略快一点 - SSD 上差距缩小,但依然存在;NVMe 上可能只差 2–3 倍
- 测试前用
sync命令清空系统页缓存(Linux),否则首次运行快、后续变慢,结果不可复现
缓冲区大小对 bufio.Reader/Writer 性能影响极大
默认 bufio.NewReaderSize(f, 4096) 在读大文件时明显拖慢吞吐。实测中,把 buffer 从 4KB 改成 64KB,顺序读取 1GB 文件的吞吐可提升 30%–50%,尤其在机械盘上。但 buffer 不是越大越好:超过 1MB 后收益趋平,还可能因内存分配抖动拉高 p99 延迟。
立即学习“go语言免费学习笔记(深入)”;
- 写场景同理:
bufio.NewWriterSize(f, 65536)比默认快,但记得在循环末尾调w.Flush() - 避免在每次
b.N迭代里重复make([]byte, size),提前分配好复用 - 用
io.CopyN或io.ReadFull替代Read,减少部分读导致的 syscall 次数 - 注意:
bufio.Scanner默认 64KB 缓冲,但按行切分有额外开销,测纯吞吐别用它
临时文件路径和 fsync 调用位置决定结果是否可信
用 os.CreateTemp("", "bench-*.txt") 创建临时文件看似干净,但若没显式 defer os.Remove(...),多次运行后磁盘碎片增加,性能逐渐下降。更关键的是:是否在每次写操作后调用 f.Sync()?如果只在最后 sync 一次,测出来的是“累积写入+单次刷盘”,不是“每写一块就落盘”的真实延迟。
- 测 latency(如日志写入):每次
Write后立刻f.Sync() - 测 throughput(如批量导出):写完再
f.Sync(),但要在b.ResetTimer()后做 - 路径尽量固定,比如
/tmp/bench-data.bin,避免os.CreateTemp的随机名带来目录查找开销 - Linux 下可用
drop_caches=3清页缓存,macOS 用sudo purge,否则缓存干扰太大
文件 I/O 基准最易被忽略的点:你根本不知道自己在测什么——是内核缓冲区拷贝速度?是磁盘寻道时间?还是 Go runtime 的 goroutine 调度开销?先明确目标(延迟 or 吞吐),再选同步策略、缓冲尺寸和清理方式,否则数字只是幻觉。











