应使用 github.com/edsrzf/mmap-go 而非 syscall.Mmap,因其自动处理页对齐、跨平台差异、错误转换及并发安全;写入后必须显式调用 mm.Flush() 才能持久化,defer mm.Close() 仅释放映射不刷盘。

Go 语言里用 mmap 高效写入大文件,核心就一条:别碰 syscall.Mmap,直接上 github.com/edsrzf/mmap-go,否则 Windows 上必 panic,Linux/macOS 上大概率 SIGBUS。
为什么 mmap.Write 比 os.File.WriteAt 快
关键在页缓存和延迟刷盘:mmap 把文件映射进虚拟内存,写操作本质是改内存页,由内核按需回写(msync 控制时机);而 WriteAt 每次都要走 VFS 层、拷贝用户态数据到内核 buffer。随机写 >1GB 文件时,吞吐常高出 3–5 倍。
但注意三点:
-
MAP_PRIVATE下修改不自动落盘,崩溃或断电可能丢数据 -
MAP_SYNC可强制同步,但仅部分文件系统支持,性能退化接近WriteAt - 频繁小写(如每字节改一次)反而更慢——缺页中断开销太大
如何安全打开并写入映射文件
必须匹配文件打开模式与映射权限,混用直接触发 SIGBUS 或 invalid argument:
立即学习“go语言免费学习笔记(深入)”;
- 只读映射:用
os.Open("file")+mmap.RDONLY - 读写映射:必须用
os.OpenFile("file", os.O_RDWR, 0)+mmap.RDWR - 想扩大文件?得先调
syscall.Ftruncate(int(f.Fd()), newSize),mmap不负责扩容 - 映射前务必调
f.Stat().Size()拿真实大小,别信配置项或文件名暗示的“2GB”
写入后数据为什么没落盘
mmap-go 默认用 MAP_SHARED,修改会反映到文件,但仅驻留在 page cache —— 程序退出、崩溃或断电,没刷过的数据就丢了。这不是 bug,是 mmap 的语义设计。
必须显式调用 mm.Flush()(等价于 msync(MS_SYNC)):
-
defer mm.Close()只做munmap,不触发同步 - 高频小写建议批量改完再
Flush(),避免阻塞;低延迟要求场景才每改必刷 - 如果只是临时缓存(如解压中间态),需私有映射(
MAP_PRIVATE),但mmap-go不暴露 flag 控制,得退回裸系统调用
容易被忽略的三个硬伤点
不是代码写错,而是 mmap 行为本身带来的隐性约束:
- 映射后返回的
[]byte不能append或重切超出mm.Len(),越界读结果未定义,写只读区域直接SIGBUS - 一次性
mmap整个几十 GB 文件,既可能耗尽虚拟地址空间(尤其 32 位环境),又会因随机访问引发大量缺页中断,实际性能反而不如os.ReadAt - 映射超大文件前务必检查
/proc/sys/vm/max_map_count,超限报cannot allocate memory,需提前sysctl -w vm.max_map_count=262144


















