mmap 比 bufio.scanner 快得多,因其将文件直接映射进虚拟内存,避免内核态到用户态拷贝;而 bufio.scanner 需频繁系统调用、内存分配与解析,gb级日志下性能极差,且不支持随机查找。

为什么 mmap 比 bufio.Scanner 读日志快得多
因为 mmap 把文件直接映射进虚拟内存,跳过了内核态到用户态的数据拷贝;而 bufio.Scanner 每次读一行都要系统调用 + 内存分配 + 字符串解析,GB 级日志下就是性能灾难。尤其当你需要按时间戳、请求 ID 或行号随机查找时,bufio.Scanner 只能从头扫,而 mmap 配合索引结构(比如每 1MB 块首行时间戳数组)可二分定位,延迟压到微秒级。
别手写 syscall.Mmap,优先用 mmap-go 库
直接调 syscall.Mmap 在 Windows 上必然失败(返回 ENOSYS),Linux/macOS 上极易 panic "invalid argument" —— 这不是代码写错,而是它不校验页对齐、不转换错误码、不处理平台差异。真实文件大小可能和配置里写的 “1GB” 差几十 MB,syscall.Mmap 会照单全收然后崩给你看。
推荐用 github.com/edsrzf/mmap-go:
-
mmap.Open("access.log")自动按f.Stat().Size()映射整个文件,无需手动算长度 - 返回的
mmap.Map可直接当[]byte用,len(mm)和mm.Len()都准确 - 内部已处理
MAP_POPULATE(预加载页)、页对齐、错误包装,SIGBUS概率大幅降低 - 支持并发读,无需加锁;写入需显式传
mmap.RDWR并调mm.Flush()
如何在 mmap 数据里安全地按行或关键字查找
别把整个 []byte 转成 string 再用 strings.Split —— 这会触发完整内存拷贝,失去 mmap 意义。正确做法是遍历字节找 '\n' 或 "\r\n":
- 用
bytes.Index(data, []byte("ERROR"))直接搜索关键字,返回的是文件偏移,可直接切片上下文 - 最后一行可能无换行符,需判断
data[len(data)-1] != '\n' - 行边界计算用
bytes.LastIndex(data[:pos], []byte("\n")) + 1,不是字符串操作 - 避免在 mmap 区域调
regexp.MustCompile,匹配时若用FindAllIndex,返回的[2]int可直接当data[start:end]用,零拷贝
映射超大日志文件时最容易被忽略的三个点
映射本身不加载全部内容,但首次访问某页会触发缺页中断——如果你顺序扫完整个 50GB 映射,性能未必比 bufio.NewReader 好,甚至更差(因少了预读优化);mm.Bytes() 返回的切片不能 append 或重切超出 mm.Len(),越界读结果未定义,写只读区域直接 SIGBUS;映射 >100GB 文件前务必检查 /proc/sys/vm/max_map_count,否则报 cannot allocate memory,这不是 Go 代码问题,是系统限制。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











