std::chrono::high_resolution_clock测文件写延迟不准,因其仅测量系统调用返回时间而非真实落盘耗时,且跨平台实现不一;真实i/o延迟需用perf或ebpf跟踪block事件,或严格控制变量用o_direct|o_sync配合clock_monotonic测量。

用 std::chrono::high_resolution_clock 测文件写延迟,为什么不准
因为 std::chrono::high_resolution_clock 在不同平台底层实现不同:Windows 上常映射到 QueryPerformanceCounter(纳秒级),Linux 上多数 libc 实现里它只是 std::chrono::steady_clock 的别名(通常基于 CLOCK_MONOTONIC,精度够但不等于硬件时钟分辨率)。更关键的是——它只管“时间”,不管“事件”。你测的不是「写操作耗时」,而是「write() 系统调用返回前那段代码执行时间」,而实际磁盘写入很可能还在内核队列或页缓存里没落盘。
- 同步写(
O_SYNC或fsync())才能逼近真实 I/O 延迟,但代价是性能暴跌 - 异步写 +
high_resolution_clock测出来的是「用户态到内核提交」的延迟,和磁盘物理响应无关 - 即使用了
O_DIRECT,仍受文件系统层(如 ext4 journal)、块设备调度器(如 mq-deadline)影响
真正能反映写延迟的监控点在哪
用户态代码无法直接观测磁盘控制器完成写入的那一刻。靠谱路径只有两条:走内核可观测性接口,或用专门工具做旁路采样。
-
perf跟踪block:block_rq_issue和block:block_rq_complete事件,差值即为 I/O 栈全程延迟(含队列、调度、传输、完成) - eBPF 程序挂钩
tracepoint:block:block_rq_issue和tracepoint:block:block_rq_complete,在内核态计算差值并导出,避免用户态时间戳抖动 - 不要依赖
/proc/diskstats,它只给累计计数,没有单次延迟信息
如果必须用 C++ 写一个近似测量,该怎么组织
目标不是绝对精确,而是控制变量、可复现、能横向对比。重点不是换更“高精度”的时钟,而是让测量边界清晰。
- 用
O_DIRECT | O_SYNC打开文件,绕过页缓存并强制落盘(注意对齐:buffer 和 offset 都需按ioctl(fd, BLKSSZGET, &bs)对齐) - 每次写前调用
clock_gettime(CLOCK_MONOTONIC, &ts_start),写完立即跟clock_gettime(CLOCK_MONOTONIC, &ts_end)(比high_resolution_clock更可控) - 禁用 CPU 频率调节:
echo performance | sudo tee /sys/devices/system/cpu/cpu*/cpufreq/scaling_governor - 避免 std::cout/std::cerr,它们会触发锁和缓冲,改用
write(2)直接输出到stderr
// 示例片段:对齐检查 int bs; ioctl(fd, BLKSSZGET, &bs); assert(((uintptr_t)buf % bs) == 0); assert((offset % bs) == 0);
常见错误现象和对应原因
很多人测出来延迟忽高忽低、甚至出现负值,基本都掉进这几个坑里。
- 测了
write()但没fsync()→ 大部分时间花在缓存里,结果是 sub-microsecond,毫无意义 - 用
std::chrono::system_clock→ 受 NTP 调整影响,时间可能倒流 - buffer 在栈上分配且未初始化 → 触发 page fault,首次写被卡住几十毫秒
- 在容器里跑,cgroup io.weight 或 io.max 限速生效 → 延迟突然跳到百毫秒以上,但程序完全感知不到
- 没绑核(
sched_setaffinity),线程在 CPU 间迁移 → cache miss + TLB flush 拉高延迟毛刺
真实磁盘写延迟不是单一数值,它是个分布。关注 P99、P99.9 而非平均值,而这些尾部延迟往往由中断延迟、I/O 调度竞争、甚至 SSD FW GC 触发——C++ 代码本身只能控制到系统调用这一层,再往下就得靠 eBPF 或 ftrace 了。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











