应优先使用 github.com/edsrzf/mmap-go 而非 syscall.mmap,因其自动处理页对齐与平台差异;映射后必须显式调用 mm.flush() 才能持久化写入,defer mm.close() 仅释放映射不刷盘。

syscall.Mmap 映射失败:长度和权限不匹配是主因
直接调用 syscall.Mmap 失败时 panic "invalid argument",几乎都不是代码写错,而是参数与文件实际状态冲突。最常踩的坑是:映射长度硬写成 1024 * 1024 * 1024 这类常量,但文件当前大小可能只有 500MB——内核拒绝映射超长区域。
- 必须先调
f.Stat()拿真实Size(),再传给syscall.Mmap - 只读映射得用
os.Open打开 +syscall.PROT_READ;写入必须用os.OpenFile(f, os.O_RDWR, 0)+syscall.PROT_READ | syscall.PROT_WRITE - 想写入并扩大文件?得先
syscall.Ftruncate(int(f.Fd()), newSize),Mmap不会自动扩容 - Windows 下完全不能用
syscall.Mmap,得切到golang.org/x/sys/windows的CreateFileMapping+MapViewOfFile
mmap-go 写入后数据不落盘:Flush() 不是可选项
用 github.com/edsrzf/mmap-go 开箱即用,但默认行为容易让人误以为“改了切片就等于写进磁盘”。其实不是:修改只驻留在 page cache,断电或崩溃即丢失。
-
mm, err := mmap.Open("file.dat", mmap.RDWR)默认是MAP_SHARED,改内存会反映到文件,但不保证落盘 - 关键写入后必须显式调
mm.Flush()(等价于msync(MS_SYNC)),不能依赖defer mm.Close()——Close()只做munmap,不触发同步 - 若只是临时缓存(如解压中间态),可用
MAP_PRIVATE,但该库不暴露 flag 控制,得退回syscall.Mmap自行构造
随机读 vs 顺序扫描:mmap 不是万能加速器
mmap 在大文件场景下不是银弹,性能优势高度依赖访问模式。乱用反而比 os.File.ReadAt 或 bufio.Reader 更慢。
- 适合:频繁跳转访问固定偏移(如日志按行号查、数据库索引定位)——内核按需加载页,省去整块拷贝
- 不适合:顺序遍历整个 50GB 文件——缺页中断密集,TLB 压力大,
bufio.NewReader(f).Read更稳 - 小文件(read,还多一层页表管理
- 超大映射(>100GB)可能撞
/proc/sys/vm/max_map_count,报cannot allocate memory,需提前sysctl -w vm.max_map_count=262144
越界访问 SIGBUS:不是 panic,是进程被 OS 直接终止
用 unsafe.Slice 或 mm.Bytes() 后直接当普通切片操作,一旦越界或文件被截断,Go 不抛 panic: runtime error,而是触发硬件异常,OS 发送 SIGBUS 终止进程——无法 recover。
- 常见诱因:用
len(m)判断长度,但实际映射大小 ≠ 文件大小(比如ftruncate截短后未重新 mmap) - 切片
append到映射底层数组会越界,Go 不检查 mmap 区域边界 - 修复方法:始终用
mm.Len()(mmap-go 提供)而非len(mm);写入前确认mm.IsWritable() - 调试技巧:
strace -e trace=mmap,munmap,write,read看系统调用是否符合预期
跨平台时 Windows 的 64KB 对齐限制、Linux 上 MAP_POPULATE 的预加载优化、以及 mmap 后 goroutine 被调度到其他线程导致的 SIGBUS,这些细节在真正压测前很难暴露。别等到上线才发现。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











