8.2 npy二进制与内存映射


文档摘要

8.2 npy二进制与内存映射 本节摘要:savetxt/loadtxt 人类可读但慢且吃内存;save/load 的 npy 格式原样落盘、读回即用,快一到两个数量级;savezcompressed 压缩多数组;np.memmap 把磁盘文件映射为数组,按需换页,能处理远超内存的大文件。本节对比四种格式并给出大文件分块处理的完整案例。 四种存取方式横评 文本慢的原因与第 4 章同源:每行都要做"字符串到浮点"的解析装箱。npy 快是因为它存的就是内存映像加一小段头信息,load 基本等于把字节块搬进内存。 格式选择决策 格式选择决策 memmap:比内存还大的文件 np.memmap 把磁盘文件当成数组的后端存储,访问哪页操作系统才调入哪页: 案例:超大文件分块求均值 完整过程。

8.2 npy二进制与内存映射

本节摘要:savetxt/loadtxt 人类可读但慢且吃内存;save/load 的 npy 格式原样落盘、读回即用,快一到两个数量级;savez_compressed 压缩多数组;np.memmap 把磁盘文件映射为数组,按需换页,能处理远超内存的大文件。本节对比四种格式并给出大文件分块处理的完整案例。

四种存取方式横评

import numpy as np import time rng = np.random.default_rng(3) data = rng.rand(2_000_000).astype(np.float64) # 约 16MB # 文本:人类可读,双向都慢 t0 = time.perf_counter() np.savetxt("demo_data.txt", data[:100000]) # 只写前10万行做演示 t1 = time.perf_counter() back = np.loadtxt("demo_data.txt") t2 = time.perf_counter() print("写10万行文本:", round(t1 - t0, 3), "秒;读回:", round(t2 - t1, 3), "秒") # 输出示例:写10万行文本: 0.121 秒;读回: 0.081 秒 # npy:二进制原样落盘 np.save("demo_data.npy", data) t0 = time.perf_counter() back2 = np.load("demo_data.npy") t1 = time.perf_counter() print("读200万行npy:", round(t1 - t0, 3), "秒") # 输出示例:读200万行npy: 0.002 秒 —— 20倍数据量,快几十倍 # npz:多数组打包,可压缩 np.savez_compressed("demo_pack.npz", features=data, labels=data[:10]) pack = np.load("demo_pack.npz") print(pack.files) # ['features', 'labels'] print(pack["labels"].shape) # (10,) # csv 一行流:genfromtxt 能处理缺失值但更慢,非结构化数值别用它

文本慢的原因与第 4 章同源:每行都要做"字符串到浮点"的解析装箱。npy 快是因为它存的就是内存映像加一小段头信息,load 基本等于把字节块搬进内存。

格式选择决策

格式选择决策

memmap:比内存还大的文件

np.memmap 把磁盘文件当成数组的后端存储,访问哪页操作系统才调入哪页:

import numpy as np # 先造一个 2GB 的文件(shape 和 dtype 写进文件头的方式用 open_memmap) big = np.lib.format.open_memmap( "big_data.npy", mode="w+", dtype=np.float32, shape=(500_000_000,) ) big[:100] = np.arange(100, dtype=np.float32) # 只写一点点 print(big[42]) # 42.0,按需换页读取 del big # flush 并解除映射,进程内存几乎没涨 # 重新以读模式映射,用完即走 mm = np.memmap("big_data.npy", dtype=np.float32, mode="r") print(mm.shape, mm[:3]) # (500000000,) [0. 1. 2.]

案例:超大文件分块求均值

完整过程。背景:20 亿个 float32 观测(约 8GB)要算均值与最大值,机器只有 16GB 内存,直接 np.load 会连开销一起爆。

操作:

import numpy as np # 构造 8GB 数据文件(演示时可用小一号参数) path = "survey.npy" n, chunk = 2_000_000_000, 100_000_000 np.lib.format.open_memmap(path, mode="w+", dtype=np.float32, shape=(n,)) mm = np.memmap(path, dtype=np.float32, mode="r") total, count, maximum = 0.0, 0, -np.inf for start in range(0, n, chunk): block = np.asarray(mm[start:start + chunk]) # 每次只实拿一个块 total += block.sum(dtype=np.float64) # 累加用宽类型,防精度流失 count += block.size maximum = max(maximum, block.max()) print("均值:", round(total / count, 4)) # 输出示例:均值: 0.0 print("最大:", maximum) # 输出示例:最大: -inf(未填充数据为0时另计)

结果与解读:峰值内存由 chunk(约 400MB)决定而非文件大小,块内 sum 仍走 NumPy 快速路径,块间用 float64 累加器保精度——第 3 章的精度纪律与第 6 章的 out 思想在真实管线里会合。变式:方差用 Welford 思路分块更新,或两遍扫描(先均值后方差);多进程时让每个进程 memmap 同一文件,页缓存由操作系统统一调度,天然共享。

⚠️ 常见坑:memmap 的 mode 别写错。r+ 模式下的写入直接落盘,改坏了没有撤销;做完就 del 或 close,长期持有映射会占着文件句柄。另外 loadtxt 读大 csv 的内存峰值约为文件数倍,大文件先转 npy 再处理是标准路线。

本节要点回顾

  • 格式分工:txt 给人看,npy 给机器用(快一到两个数量级、dtype 保全),npz 压缩打包,memmap 破内存上限
  • npy 本质:内存映像加头部,读写近乎字节搬运,中间结果首选
  • memmap 机制:按需换页,峰值内存由访问窗口决定,mode 决定读写权限
  • 分块统计模板:memmap 加切片加宽类型累加器,任何超大文件统计通用
  • 先转 npy 再处理:大 csv 直接 loadtxt 是内存与速度的双重事故

最后一节把全书知识收进一张性能清单,完成从"会用"到"会诊"的跨越。


作者与出处
原作者: 灏天文库
来源:灏天文库
整理: 灏天文库整理
由灏天文库平台收录,内容或由平台用户上传,仅供学习交流
发布者: 作者: 灏天文库 转发
评论区 (0)
U