如何有效地读取和写入太大而无法放入内存的文件?

Posted

技术标签:

【中文标题】如何有效地读取和写入太大而无法放入内存的文件?【英文标题】:How can I efficiently read and write files that are too large to fit in memory? 【发布时间】:2015-11-16 16:41:52 【问题描述】:

我正在尝试计算 100,000 个向量的余弦相似度,每个向量都有 200,000 个维度。

通过阅读其他问题,我知道memmap、PyTables 和 h5py 是我处理此类数据的最佳选择,我目前正在使用两个 memmap;一个用于读取向量,另一个用于存储余弦相似度矩阵。

这是我的代码:

import numpy as np
import scipy.spatial.distance as dist

xdim = 200000
ydim = 100000

wmat = np.memmap('inputfile', dtype = 'd', mode = 'r', shape = (xdim,ydim))
dmat = np.memmap('outputfile', dtype = 'd', mode = 'readwrite', shape = (ydim,ydim))

for i in np.arange(ydim)):
    for j in np.arange(i+1,ydim):
        dmat[i,j] = dist.cosine(wmat[:,i],wmat[:,j])
        dmat.flush()

目前,htop 报告我正在使用 224G 的 VIRT 内存和 91.2G 的 RES 内存,并且正在稳步攀升。在我看来,在该过程结束时,整个输出矩阵将存储在内存中,这是我试图避免的。

问题: 这是内存映射的正确用法吗,我是否以内存有效的方式写入输出文件(我的意思是只有输入和输出文件的必要部分,即dmat[i,j]wmat[:,i/j],存储在内存中)?

如果没有,我做错了什么,我该如何解决?

感谢您的任何建议!

编辑:我刚刚意识到 htop 报告的系统总内存使用量为 12G,所以它似乎毕竟在工作……有谁能启发我吗? RES 现在是 111G...

EDIT2:memmap 是从一个一维数组创建的,该数组由许多非常接近 0 的长小数组成,其形状为所需的尺寸。然后 memmap 看起来像这样。

memmap([[  9.83721223e-03,   4.42584107e-02,   9.85033578e-03, ...,
     -2.30691545e-07,  -1.65070799e-07,   5.99395837e-08],
   [  2.96711345e-04,  -3.84307391e-04,   4.92968462e-07, ...,
     -3.41317722e-08,   1.27959347e-09,   4.46846438e-08],
   [  1.64766260e-03,  -1.47337747e-05,   7.43660202e-07, ...,
      7.50395136e-08,  -2.51943163e-09,   1.25393555e-07],
   ..., 
   [ -1.88709000e-04,  -4.29454722e-06,   2.39720287e-08, ...,
     -1.53058717e-08,   4.48678211e-03,   2.48127260e-07],
   [ -3.34207882e-04,  -4.60275148e-05,   3.36992876e-07, ...,
     -2.30274532e-07,   2.51437794e-09,   1.25837564e-01],
   [  9.24923862e-04,  -1.59552854e-03,   2.68354822e-07, ...,
     -1.08862665e-05,   1.71283316e-07,   5.66851420e-01]])

【问题讨论】:

我不会说这个问题对于 SO 来说是“错误的”,但您可能会在 codereview.stackexchange.com 得到更好的答案,因为这更多的是关于架构而不是实际的错误或操作方法。跨度> @Victory CR 更多的是关于代码而不是架构。并不是说 CR 是“错误的”,但我认为 OP 可能会在 SO 上得到更好的答案。 :) 好吧,基本上我是在询问如何有效地从磁盘读取/写入大文件。我很困惑,因为我从 htop =S 获得了相互矛盾的信息 如果你需要分散在多台机器上,并且你想在python中运行,请查看apache spark。 您可以添加输入文件的样本吗?另外,如果您不想存储在内存中,为什么要写入 memmap,为什么不直接写入磁盘? 【参考方案1】:

就内存使用而言,您目前所做的并没有什么特别的问题。内存映射数组在操作系统级别处理 - 要写入的数据通常保存在临时缓冲区中,并且仅在操作系统认为有必要时才提交到磁盘。在刷新写入缓冲区之前,您的操作系统绝不应该让您耗尽物理内存。

我建议不要在每次迭代时调用flush,因为这违背了让您的操作系统决定何时写入磁盘以最大化效率的目的。目前,您一次只能写入单个浮点值。


就 IO 和 CPU 效率而言,一次只运行一条线路几乎可以肯定是次优的。对于大的、连续的数据块,读取和写入通常更快,同样,如果您可以使用矢量化一次处理多行,您的计算可能会更快。一般的经验法则是处理尽可能大的数组块(包括在计算期间创建的任何中间数组)。

另一件可以产生巨大影响的事情是输入和输出数组的内存布局。默认情况下,np.memmap 为您提供一个 C 连续(行优先)数组。因此,按列访问wmat 效率将非常低,因为您正在处理磁盘上的非相邻位置。如果wmat 在磁盘上是 F-contiguous(column-major),或者如果您按行访问它,您会更好。

同样的一般建议适用于使用 HDF5 而不是 memmap,但请记住,使用 HDF5 您必须自己处理所有内存管理。

【讨论】:

要获得一个 F 连续的列优先数组,使用order = 'F' 创建 memmap 就足够了吗?感谢您的详细描述。链接中的代码看起来也很棒,我会尝试一下。 这对您的示例没有帮助,因为wmat 是您以只读模式打开的磁盘上预先存在的阵列。您实际上必须以列主要格式将wmat 写入磁盘。 啊,我明白了……我以后会记住这一点的。最后一个问题,是否有任何令人信服的理由使用 HDF5 而不是 memmaps? 速度、压缩、可移植性……Joe Kington 的回答 here 很好地说明了优缺点。【参考方案2】:

内存映射顾名思义:将(虚拟)磁盘扇区映射到内存页面。内存由操作系统按需管理。如果内存足够,系统会将部分文件保留在内存中,可能会填满整个内存,如果内存不够,系统可能会丢弃从文件中读取的页面或将它们交换到交换空间中。通常你可以依赖操作系统尽可能高效。

【讨论】:

我明白了,它正在尽可能高效地使用内存。感谢您清除它!你知道为什么 VIRT 是 224G 而 RES 现在稳定在 149G 系统使用量只有 12G 吗?

以上是关于如何有效地读取和写入太大而无法放入内存的文件?的主要内容,如果未能解决你的问题,请参考以下文章

C & MinGW:Hello World 给我错误“程序太大而无法放入内存”

SQLiteBlobTooBigException:写入数据库时​​行太大而无法放入 CursorWindow

异步写入 R 中的文件

如何在 C# 中有效地从 SQL 数据读取器写入文件?

java怎么将生成的文件放入内存?

优化Scala代码以读取不适合内存的大文件的有效方法