直接write()变慢因页不对齐触发内核缓冲拷贝;需用posix_memalign分配页对齐内存、O_DIRECT绕过page cache,并严格校验地址/偏移/长度对齐及write返回值。

为什么直接 write() 会导致写入变慢甚至卡顿
操作系统内核对磁盘 I/O 的优化依赖于页对齐(通常 4KB),如果 std::vector 的数据起始地址或长度未对齐,write() 系统调用可能触发内核内部的“缓冲拷贝 + 拆分写入”,尤其在大块数据(>64KB)场景下,延迟跳变明显。更隐蔽的问题是:当 vector 内存由 malloc() 分配(默认行为),其地址天然不保证页对齐,哪怕你手动 reserve() 也没用。
用 posix_memalign() 分配页对齐的 vector 底层内存
标准 std::vector 不支持自定义对齐的 allocator,但你可以绕过它,用 posix_memalign() 手动分配对齐内存,再用 std::vector 的迭代器构造方式“接管”这块内存——注意:这不是常规用法,仅适用于只写、生命周期明确的流式场景。
- 先调用
posix_memalign(&ptr, 4096, size)获取对齐指针ptr(4096 是典型页大小) - 用
std::vector<uint8_t>(static_cast<uint8_t>(ptr), static_cast<uint8_t>(ptr) + size)</uint8_t></uint8_t></uint8_t>构造视图(C++17 起支持) - 务必确保后续不调用
push_back()、resize()等会重新分配的操作,否则内存泄漏或崩溃 - 写完后必须用
free(ptr)释放,不能用delete或依赖 vector 析构
用 pread/pwrite + O_DIRECT 绕过 page cache 直写磁盘
若目标是“高速流式持久化”,尤其是持续写入 GB 级数据时,page cache 反而成为瓶颈(缓存污染、脏页回写抖动)。启用 O_DIRECT 标志可让内核跳过缓存,但强制要求:缓冲区地址对齐、偏移对齐、长度是扇区倍数(通常是 512B)。三者缺一不可,否则 write() 返回 -1 并设 errno = EINVAL。
- 打开文件时加
O_DIRECT:open("data.bin", O_WRONLY | O_DIRECT | O_CREAT, 0644) - 每次
pwrite()的offset必须是 4096 的整数倍(页对齐),不是 512 - 传入的 buffer 地址(即
posix_memalign返回值)必须页对齐,且 buffer 长度也需是 512 的整数倍 - 推荐单次写入 128KB ~ 1MB,太小则系统调用开销占比高,太大则阻塞时间不可控
实际写入时别忽略 write() 返回值和 errno
即使做了全部对齐,O_DIRECT 下仍可能部分写入(比如磁盘满、设备忙),write() 返回值可能小于请求长度。常见错误是直接假设“返回非负就写完了”。另外,O_DIRECT 对某些文件系统(如 ext4 的 default mount option)或 loop 设备可能静默退化为普通写,建议用 strace -e trace=write,pwrite64 验证是否真走 direct path。
立即学习“C++免费学习笔记(深入)”;
- 必须循环检查
write()返回值,累加已写长度,直到全部完成 - 遇到
errno == EINTR重试;errno == EAGAIN或EWOULDBLOCK(仅非阻塞 fd)需等待 - 写入前用
fcntl(fd, F_SETFL, O_NONBLOCK)可避免阻塞,但要自己处理重试逻辑 - 最后用
fsync()强制落盘——别省这一步,否则断电可能丢最后一批数据
页对齐本身不难,难的是把对齐贯穿到内存分配、I/O 参数、错误处理三个环节,漏掉任一环都会让性能回归普通 write 水平,甚至更差。真正高速的前提,是让整个链路都“知道”你在对齐——从 malloc 替换到 open 标志再到 write 循环,每个点都得显式确认。


















