Python joblib 的内存映射序列化

1次阅读

joblib.dump 不支持 mmap_mode 参数,该参数仅对 joblib.load 有效;正确用法是 dump 时不传,load 时指定 ‘r’ 或 ‘c’ 以实现大数组内存映射。

Python joblib 的内存映射序列化

joblib.dump 用 mmap_mode='r' 为什么反而报错?

因为 mmap_mode 只对 joblib.load 生效,dump 时传了也直接忽略。常见错误是看到文档里写了 mmap_mode,就顺手塞进 dump 调用里,结果既没效果,还可能掩盖真正问题。

真正起作用的场景是:先用 joblib.dump(obj, 'data.pkl') 存好,之后在 load 时加 mmap_mode='r''c',让大数组不全载入内存。

  • mmap_mode='r':只读映射,适合只读分析,进程退出后自动释放
  • mmap_mode='c'copy-on-write,允许修改但不写回磁盘,适合临时计算
  • 如果文件被其他进程锁住、或路径不存在、或权限不足,load 会抛 OSError: [errno 22] Invalid argument
  • Windows 上对 mmap 支持较弱,尤其 NTFS 压缩文件夹或 onedrive 同步目录里,容易触发 ValueError: mmap Length is greater than file size

哪些对象能从 mmap_mode 真正受益?

只有内部含大型 numpy.ndarray(且 dtype 是基础类型如 float64int32)的对象才走内存映射路径。普通 dict、list、pandas DataFrame(底层虽用 ndarray,但 joblib 默认不 mmap 其 block)、自定义类实例——统统不映射,还是全量反序列化。

验证方法:用 joblib.load('x.pkl', mmap_mode='r') 加载后,检查关键数组的 .base 是否为 mmap.mmap 实例:

立即学习Python免费学习笔记(深入)”;

import numpy as np arr = joblib.load('big_array.pkl', mmap_mode='r') print(type(arr.base))  # 应该是 <class 'mmap.mmap'>
  • 小数组(默认小于 1MB)即使指定 mmap_mode 也不会映射,joblib 认为不值得开销
  • 如果数组是 view(比如 a[::2]),则 .base 指向原 mmap,但自身仍算“映射数组”
  • 混用不同 dtype 的结构化数组(如 np.dtype([('x', 'f4'), ('y', 'i8')]))可能 fallback 到普通加载

多进程共享同一个 joblib 文件,mmap_mode='r' 安全吗?

安全,前提是所有进程都只读。多个进程同时以 mmap_mode='r' 打开同一文件,底层共用同一段物理内存页,零拷贝、低延迟、省内存。

但要注意:

  • 不能有任一进程用 mmap_mode='r+''w+',否则触发写时复制(COW)或直接报错
  • 文件不能被外部程序(如文本编辑器、ide)意外修改,否则 mmap 区域内容会突变,导致静默计算错误
  • linux 下若文件被 truncate 缩小,已映射区域访问会触发 signal 7 (SIGBUS)python 进程直接崩溃,无 traceback
  • joblib 不做文件锁,靠使用者保证“写完再读”,推荐用原子重命名(os.replace())更新文件

替代方案:什么时候该放弃 joblib mmap,改用 np.memmap

当你要精细控制数据布局、需要随机访问子区间、或必须跨语言(如 C/Fortran)读取时,joblib封装反而碍事。它把 mmap 隐藏在 pickle 流里,没法跳过 header 直接寻址。

例如处理 TB 级遥感影像切片,常用做法是:

arr = np.memmap('data.bin', dtype='float32', mode='r', shape=(10000, 10000)) tile = arr[1000:1100, 2000:2100]  # 零拷贝切片
  • np.memmap 明确分离“文件布局”和“逻辑视图”,调试时可直接用 hexdump 查看二进制
  • joblib 的 mmap 是 pickle 协议的一部分,无法用非 Python 工具解析
  • 如果你的 pipeline 里已有大量 np.memmap 逻辑,硬套 joblib 反而增加序列化/反序列化开销

复杂点在于:joblib 的 mmap 是“透明”的,你几乎感觉不到;而 np.memmap 要自己管 shape、dtype、offset,错一点就 segfault。别为了省几行代码,把内存地址当玩具玩。

text=ZqhQzanResources