必须用 mmap.open() 而非 syscall.mmap,因后者不校验页对齐、不转换错误码、不处理平台差异,易在 windows 失败、linux panic;推荐 github.com/edsrzf/mmap-go,自动按文件大小映射、支持切片访问、内置 map_populate 和错误处理,稀疏跳转访问更快,但需注意虚拟内存限制与顺序遍历性能下降。

只读搜索必须用 mmap.Open(),别碰 syscall.Mmap
直接调 syscall.Mmap 在 Windows 上必然失败,在 Linux 上极易 panic: invalid argument,根本原因不是代码写错,而是它不校验页对齐、不转换错误码、不处理平台差异。真实文件大小可能和配置里写的 “1GB” 差几十 MB,syscall.Mmap 会照单全收然后崩给你看。
正确做法是用 github.com/edsrzf/mmap-go:
- mmap.Open("data.idx") 自动按 f.Stat().Size() 映射整个文件,无需手动算长度
- 返回的 mmap.Map 可直接当 []byte 用,len(mm) 和 mm.Len() 都准确
- 内部已处理 MAP_POPULATE(预加载页)、页对齐、错误包装,SIGBUS 概率大幅降低
随机跳转查偏移时,mm[off:off+size] 比 mm.ReadAt(buf, off) 更快
如果你在做日志行号定位、数据库索引查找、或固定结构的二进制记录扫描,直接用切片语法访问比走 io.ReaderAt 接口少一层函数调用和参数检查,实测延迟低 10%–20%。
但必须注意:
- 访问前确认 off + size ,别信“文件应该没被截断”这种假设<br>
- 不要用 <code>append() 向映射底层数组追加数据——Go 不拦截 mmap 区域越界,触发的是 SIGBUS 而非 panic
- 若需多次小范围读取,建议提前用 unsafe.Slice(unsafe.Add(unsafe.Pointer(&mm[0]), offset), length)(Go 1.21+)构造子视图,避免重复计算指针
写入打标记必须配 mmap.RDWR + mm.Flush()
只读映射下写内存会立即 SIGBUS;即使开了 mmap.RDWR,改完不 mm.Flush(),数据就卡在 page cache 里,程序崩溃或断电即丢失。
关键点:
- 打开文件必须用 os.OpenFile(name, os.O_RDWR, 0),不能用 os.Open()
- 显式传 mmap.RDWR: mmap.OpenFile(f, mmap.RDWR)
- 每次关键写入后立刻 mm.Flush()(等价于 msync(MS_SYNC)),别依赖 defer mm.Close() —— Close() 只做 munmap,不刷盘
- 想扩大文件?先 syscall.Ftruncate(int(f.Fd()), newSize),mmap 不自动扩容
映射超大文件前务必检查 /proc/sys/vm/max_map_count
映射 >100GB 文件时,内核可能拒绝分配虚拟内存,报 cannot allocate memory,这不是 Go 代码问题,是系统限制。
临时修复:
- sysctl -w vm.max_map_count=262144
- 或写入 /etc/sysctl.conf 持久化
- 注意:该值影响所有进程,别设得过大导致 OOM
另外,顺序遍历整个 50GB 文件时,mmap 反而更慢——缺页中断太密集,TLB 压力大,此时 bufio.NewReader(f).Read() 更稳。mmap 的优势只在稀疏、跳转、固定偏移访问时成立。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











