必须用 go test -bench=. 做基准测试并清缓存、关预读、显式 sync,才能真实量化磁盘开销;os.readfile 测得的是 page cache、gc 或调度抖动,而非真实磁盘性能。

不能靠 time.Now() 包裹 os.ReadFile 或 io.Copy 测“损耗”,那测的多半是 page cache、GC 暂停或调度抖动,不是磁盘真实开销。真要量化模块对磁盘的拖累,必须用 go test -bench=. 驱动基准测试,并在云主机上做三件事:清缓存、关预读、显式 Sync。
为什么云主机上直接跑 os.ReadFile benchmark 会失真
不同云厂商(AWS EC2、阿里云 ECS、腾讯云 CVM)底层存储栈差异大:EBS 有 IO credit 机制,云盘可能走 NVMe over Fabrics,部分实例还共享宿主机磁盘带宽。此时若用 os.ReadFile 测 500MB 文件,它一次性 malloc 大内存 → 触发 GC STW → 耗时全算在“I/O”头上,实际磁盘可能只忙了 20ms。
- EC2 gp3 卷默认开启
readahead,顺序读时内核偷偷多读几 MB 到 page cache,第一次测快得离谱 - 阿里云 ESSD AutoPL 会动态调整 IOPS,但
time.Now()测不出这种波动,只看到平均值 - 腾讯云 CBS 在高并发小 IO 场景下有队列延迟,
os.ReadFile把所有小读合并成一次大读,完全掩盖排队时间
用 go test -bench=. + bufio.NewReaderSize 测裸盘吞吐
目标是让 benchmark 行为贴近生产代码——比如你模块里实际用的是 bufio.NewReaderSize(f, 64*1024),那就必须在 benchmark 里复现它,而不是换 os.ReadFile。
- 写
BenchmarkReadLargeFile:先os.Open,再套bufio.NewReaderSize(f, 64*1024),用reader.Read循环读完,别用io.ReadAll - Linux 云主机上跑前清缓存:
sudo sh -c "echo 3 > /proc/sys/vm/drop_caches",否则测的是内存带宽 - 关掉内核预读:
sudo blockdev --setra 0 /dev/nvme0n1(替换为你实例的磁盘设备名,可用lsblk确认) - 关注
ns/op和B/op:前者低说明吞吐稳,后者高(比如 >64KB/op)说明缓冲区没复用或频繁 alloc,得检查NewReaderSize的 size 是否设对
写入损耗必须包含 file.Sync() 才算数
云主机上写入性能瓶颈往往不在 Write,而在 Sync —— 它强制刷盘,暴露网络存储往返延迟(如 EBS 的 5–15ms)、云盘一致性协议开销(如阿里云 ESSD 的多副本同步耗时)。
- benchmark 中漏掉
file.Sync(),测出来的是虚假高吞吐(Write只进页缓存) -
Sync必须放在defer file.Close()之前,否则Close可能隐式触发Sync,导致耗时被归到关闭阶段 - 对延迟敏感的模块(如日志采集),建议单独写
BenchmarkWriteSyncLatency,固定写 4KB +Sync,看 P99 延迟是否超 20ms(云盘典型阈值) - 注意 Windows 云主机(如 Azure VM)需管理员权限才能调用
Sync,否则返回syscall.ERROR_ACCESS_DENIED
跨云平台比对时,唯一可信指标是 IOCounters 差值速率
别信单次 benchmark 数值——EC2 c7i.2xlarge 和阿里云 g8i.2xlarge 的 “500MB/s” 没可比性,硬件和驱动完全不同。真正能横向对比的是同一台机器上不同模块引起的 IO 增量。
- 用
github.com/shirou/gopsutil/v3/disk.IOCounters()在 benchmark 前后各采一次,计算(current.ReadBytes - prev.ReadBytes) / elapsed.Seconds() - Linux 上
ReadBytes来自/proc/diskstats第 6 列(sectors_read× 512),它绕过 page cache,反映真实下发到块设备的字节数 - Windows 云主机需以管理员身份运行,否则部分设备统计为 0;macOS 云主机(如 MacStadium)不支持 APFS 卷 IO 统计,得换物理磁盘测试
- 关键点:采样间隔必须 ≥2 秒,避免
/proc/diskstats单次更新精度不足(某些云厂商内核 patch 会降低更新频率)
最易被忽略的是:云主机上 disk.IOCounters() 返回的 ReadTime 和 WriteTime 是设备总耗时,除以操作次数才是平均延迟——这个值比吞吐更能暴露 EBS burst balance 耗尽、ESSD credit 不足等隐性瓶颈。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











