内存映射文件排序不能直接用std::sort,因mmap区域虽可随机读写但不满足其连续可写要求,且大文件全映射易OOM,文本行需解析导致长度不匹配无法原地存储;应采用分块映射+外部归并。

内存映射文件排序为什么不能直接用 std::sort
因为 std::sort 要求随机访问迭代器指向连续、可写、可寻址的内存,而 mmap 映射的区域虽支持随机读写,但若文件过大(比如几十 GB),全部映射会触发 ENOMEM;更关键的是,若原始文件是文本行(如每行一个整数),数据不是结构化数组,不能直接按 int* 解释 —— 你得先解析,而解析结果不能原地存回映射区(长度不匹配)。常见错误是:用 mmap 读完再 std::vector<int></int> 全载入内存,这反而失去“大文件”意义。
分块映射 + 外部归并才是可行路径
真正能扩展到 TB 级的方案是“内存映射分块排序 + 多路归并”,核心是控制单次映射大小,避免 OOM,同时复用映射页缓存。实操要点:
- 用
mmap每次只映射固定大小(如 128MB)的原始文件片段,配合std::string_view或自定义解析器逐行提取数值,存入std::vector<int></int>并std::sort - 排序后写入临时文件(
write()或msync(MS_SYNC)后munmap),不要尝试mmap写回原文件偏移 —— 行长度变化会导致覆盖错位 - 所有临时文件生成后,用
std::priority_queue做 k-路归并:每个临时文件打开一个ifstream,读取首行建堆,每次 pop 最小值并从对应流补充下一行 - 注意
mmap的prot参数:只读排序用PROT_READ;若需写临时映射缓冲区(如预分配输出区),则用PROT_READ | PROT_WRITE,且flags必须含MAP_PRIVATE(避免脏页刷回原文件)
mmap 对齐与边界处理容易崩在哪
典型崩溃点不是算法逻辑,而是系统调用约束被忽略:
-
offset参数必须是sysconf(_SC_PAGESIZE)的整数倍,否则mmap返回MAP_FAILED—— 别硬写0或文件偏移,要向下对齐到页首 - 映射长度
length可以小于剩余文件字节数,但必须保证offset + length ≤ 文件大小;否则read()类行为未定义,segfault可能延迟到首次访问越界页 - 文本解析时,若某行跨两个映射块(如换行符在块尾),
strchr(buf, '\n')会失败。正确做法是预留 1–2KB 重叠缓冲区,或用memchr扫描时检查是否到达buf + length - 1,若是则回退并重新映射 - Windows 下对应 API 是
CreateFileMapping+MapViewOfFile,页对齐值为GetSystemInfo().dwAllocationGranularity,不是4096
性能瓶颈往往卡在 I/O 调度而非 CPU
实测中,当临时文件数 > 8 且磁盘是 SATA SSD 时,归并阶段的 lseek + read 随机跳转会吃掉 60%+ 时间。优化方向很实际:
立即学习“C++免费学习笔记(深入)”;
- 临时文件数量控制在 4–8 个以内,靠单块增大(如 256MB/块)降低归并路数,而非盲目分更多小块
- 归并时对每个输入流使用
setvbuf(..., _IOFBF, ..., 1 设置大缓冲区,减少系统调用次数 - 若目标文件系统支持(如 XFS/ext4),用
posix_fadvise(fd, offset, len, POSIX_FADV_DONTNEED)在读完一块后显式丢弃页缓存,避免挤占后续映射空间 - 别用
std::endl写输出文件 —— 它强制flush,换成'\n'+ 定期fflush,吞吐量可提升 2–3 倍
真正的难点不在“怎么排序”,而在“怎么让 mmap 不反噬性能”:页对齐算错一次,整个流程就段错误;临时文件没关 fd,开到 1024 就 EMFILE;归并缓冲区太小,I/O 成瓶颈。这些细节比算法本身更决定成败。



















