mmap 比 fread + strstr 快得多,因其将文件直接映射为虚拟内存,避免内核态与用户态间反复拷贝;而 fread 需频繁系统调用、缓冲区分配及数据复制,对大文件导致 i/o 和内存开销剧增。

为什么 mmap 比 fread + strstr 快得多
因为 mmap 把文件直接映射成进程虚拟内存,跳过了内核态到用户态的数据拷贝;而 fread 每次读块都要触发系统调用、分配缓冲区、复制数据,对千万行文本(比如 5GB 日志)会产生数百万次小读操作,I/O 和内存开销爆炸。mmap 后,memchr、memcmp、手写 SIMD 扫描都能直接在虚拟地址上跑,CPU 缓存友好。
但注意:mmap 不等于“自动加载全部内容”——它只是建立映射,真正页加载是按需(page fault)发生的。所以首次扫描仍可能卡顿,但后续重复检索极快。
如何用 mmap 安全加载超大文件(避免 SIGBUS / ENOMEM)
Linux 下 mmap 映射大小超过可用虚拟内存(或进程 RLIMIT_AS)会失败;映射空洞文件、设备文件、或文件被截断时,访问未加载页会触发 SIGBUS。实战中必须做三件事:
- 用
stat()确认文件大小,拒绝 > 100GB 的单文件(除非你明确配置了足够 swap 和 vm.max_map_area) - 始终用
MAP_PRIVATE | MAP_POPULATE:前者避免写回磁盘,后者预加载所有页(牺牲启动时间换稳定延迟) - 映射后立即访问首尾各 4KB(如
buf[0]和buf[size-1]),强制触发 page fault 并捕获SIGBUS—— 用sigaction()注册 handler,出错就munmap+ 报错退出
示例关键片段:
int fd = open("access.log", O_RDONLY);
struct stat st; fstat(fd, &st);
void* buf = mmap(nullptr, st.st_size, PROT_READ, MAP_PRIVATE | MAP_POPULATE, fd, 0);
if (buf == MAP_FAILED) { /* 处理 ENOMEM / EACCES */ }
// 触发首尾页加载
volatile char dummy = ((char*)buf)[0] + ((char*)buf)[st.st_size - 1];
如何在 mmap 内存中高效查找多行匹配(非正则)
逐字节 memchr('\n') 找行首太慢;用 SSE4.2 的 _mm_cmpistrm 或 AVX2 的 _mm256_cmpeq_epi8 向量化找换行符,速度提升 5–8 倍。但更实用的是「行索引预构建」:
- 首次 mmap 后,单线程遍历一次,记录所有
'\n'偏移存入std::vector<size_t></size_t>(内存约 80MB/十亿行) - 后续检索时,二分查找该 vector 得到目标行范围,再用
memcmp对每行头做前缀匹配(如查"GET /api/user") - 若支持模糊匹配,可对每行计算 xxHash3(极快),建哈希表加速;但千万行下纯内存
memcmp通常已够用(实测 1.2s 跑完 5000 万行)
避免陷阱:Windows 的 CreateFileMapping 需用 FILE_MAP_READ,且映射大小不能超过 GetLargePageMinimum() 返回值(默认 2MB),得手动分段映射。
为什么不要在 mmap 区域里用 std::string 或 std::regex
std::string 构造时会内部 malloc 并 memcpy,把整行拷出来——这直接废掉 mmap 的零拷贝优势;std::regex 编译和匹配过程大量动态分配、回溯、状态机跳转,缓存不友好,且无法利用内存映射的局部性。
替代方案很直接:
- 用
char*+size_t len表示一行(例如line_start和next_line_start - line_start - 1) - 简单匹配用
strncmp(line_start, "ERROR", 5) == 0 - 复杂模式用
re2::RE2(比 std::regex 快 10x,支持RE2::Arg直接指向 mmap 区域) - 完全避免构造任何临时字符串对象
最易忽略的一点:mmap 区域的生命周期必须长于所有基于它的指针——别让 std::vector<:string></:string> 持有 char*,也别在 lambda 捕获里存裸指针后异步执行。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











