mmap 不适合处理海量小文件,因其专为单个大文件优化;逐个映射成千上万个几kb文件会引发频繁系统调用、文件描述符耗尽、内存碎片化,反而拖慢i/o。

直接说结论:mmap 不适合处理“海量小文件”,它专为单个大文件优化;用它读取成千上万个几 KB 的文件,反而会拖慢整体 I/O,还可能触发系统资源耗尽。
为什么 mmap 处理海量小文件会变慢
mmap 的优势建立在“一次映射、多次随机访问”这个前提上。每个 mmap.mmap() 调用都要经历内核页表注册、虚拟内存区域分配、文件元数据加载等开销。对 10,000 个 4KB 文件逐个调用 mmap(),等于触发 10,000 次系统级映射操作——这比直接 open().read() 还重。
常见错误现象包括:
- 进程卡在
mmap.mmap()调用上,strace显示大量mmap2()系统调用失败或阻塞 -
OSError: [Errno 24] Too many open files—— 因为每个 mmap 对象默认保持文件描述符打开(即使用了with语句,mm.close()才真正释放) - 内存 RSS 暴涨但无实际收益:每个小文件映射仍至少占用一个内存页(通常 4KB),碎片化严重
真正该用 mmap 的场景:单一大文件 + 随机访问
如果你手头是 1 个 5GB 的二进制日志、1 个 800MB 的遥感影像 raw 数据、或 1 个预打包的词典 words.dat,那 mmap 才是正解。
快速生成专业的 Python 脚本和应用代码。一键创建完整项目结构,支持CLI、API、爬虫、Bot、Django等多种项目类型,包含完整的项目结构、配置文件、依赖管理、测试、README和文档。
使用要点:
- 务必用
access=mmap.ACCESS_READ(只读)或ACCESS_WRITE(需修改),避免默认行为在 Windows 上引发意外写入 - 映射长度设为
0表示映射整个文件,但注意:Windows 不允许对空文件这么做,会抛ValueError - 如果文件初始为空,必须先用
f.seek(size-1); f.write(b'\0')扩容,否则mmap失败 - 不要在循环里反复创建/销毁 mmap 对象;复用同一个对象做多次切片访问(如
mm[1000:2000]、mm.find(b'key'))才是高效用法
海量小文件的正确加速方案
面对数万个小文件(比如日志分片 log_0001.bin~log_9999.bin),应放弃单文件 mmap 思路,改用以下组合:
- 批量合并:用
tar或自定义二进制容器把小文件打包成单个大文件,再对这个容器文件做 mmap —— 这样既保留 mmap 优势,又规避了开销 - 异步 + 缓存:用
asyncio.to_thread()包裹open().read(),配合functools.lru_cache缓存热文件内容 - 内存池预读:用
os.posix_fadvise(fd, 0, 0, os.POSIX_FADV_WILLNEED)提前通知内核加载文件页,对顺序读小文件效果显著 - 绕过 Python 层:用
pyarrow.dataset或zarr加载分块存储格式,它们底层已对小文件聚合做了深度优化
最常被忽略的一点:mmap 的“零拷贝”只发生在用户态内存与内核页缓存之间;如果文件不在 page cache 中,首次访问仍要走磁盘读——所以冷启动性能并不神奇。真正省下的,是反复 read() 时的数据复制和系统调用切换成本,不是磁盘寻道时间。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










