【问题标题】:Python and memory efficient way of importing 2d data导入 2d 数据的 Python 和内存高效方式
【发布时间】:2014-07-11 15:48:58
【问题描述】:

我正在尝试运行一些使用 Python 分析数据的脚本,但我很快就对它占用的 RAM 空间感到惊讶:

我的脚本从文件中读取两列整数。它通过以下方式导入它:

import numpy as N
from sys import argv
infile = argv[1]
data = N.loadtxt(infile,dtype=N.int32)  //infile is the input file

对于一个包含近 800 万行的文件,它需要大约 1.5 Gb 的内存(在这个阶段它所做的只是导入数据)。

我尝试在其上运行内存分析器,结果如下:

Line # Mem 使用增量行内容

 5   17.664 MiB    0.000 MiB   @profile
 6                             def func():
 7   17.668 MiB    0.004 MiB    infile = argv[1]
 8  258.980 MiB  241.312 MiB    data = N.loadtxt(infile,dtype=N.int32)

所以 250Mb 的数据,与内存中的 1.5Gb 相差甚远(什么占用了这么多空间?)

当我尝试使用 int16 而不是 int32 将其除以 2 时:

Line # Mem 使用增量行内容

 5   17.664 MiB    0.000 MiB   @profile
 6                             def func():
 7   17.668 MiB    0.004 MiB    infile = argv[1]
 8  229.387 MiB  211.719 MiB    data = N.loadtxt(infile,dtype=N.int16)  

但我只存了十分之一,怎么会?

我不太了解内存占用,但这正常吗?

另外,我在 C++ 中编写了相同的代码,将数据存储在 vector<int> 对象中,它只占用 120Mb 的 RAM。

在我看来,Python 在处理内存方面似乎占了很大的比重,它在做什么会增加数据的权重?是不是和 Numpy 关系更密切?

受以下答案的启发,我现在通过以下方式导入我的数据:

infile = argv[1]
output = commands.getoutput("wc -l " + infile) #I'm using the wc linux command to read the number of lines in my file and so how much memory allocation do I need
n_lines = int(output.split(" ")[0]) #the first int is the number of lines
data = N.empty((n_lines,2),dtype=N.int16) #allocating
datafile = open(infile)
for count,line in enumerate(datafile): #reading line by line
    data[count] = line.split(" ") #filling the array

它对多个文件的工作方式也非常相似:

infiles = argv[1:]
n_lines = sum(int(commands.getoutput("wc -l " + infile).split(" ")[0]) for infile in infiles)
i = 0
data = N.empty((n_lines,2),dtype=N.int16)
for infile in infiles:
    datafile = open(infile)
    for line in datafile:
        data[i] = line.split(" ")
        i+=1

罪魁祸首似乎是numpy.loadtxt,删除它后我的脚本现在不需要大量内存,甚至运行速度快了 2-3 倍 =)

【问题讨论】:

    标签: python memory numpy


    【解决方案1】:

    loadtxt() 方法的内存效率不高,因为它使用 Python 列表来临时存储文件内容。 Here 简要解释了 Python 列表为何占用如此多的空间。

    一种解决方案是创建您自己的用于读取文本文件的实现,如下所示:

    buffsize = 10000  # Increase this for large files
    data = N.empty((buffsize, ncols))  # Init array with buffsize
    dataFile = open(infile)
    
    for count, line in enumerate(dataFile):
       if count >= len(data):
           data.resize((count + buffsize, ncols), recheck=False)
       line_values = ... <convert line into values> ...
       data[count] = line_values
    
    # Fix array size
    data.resize((count+1, ncols), recheck=False)
    dataFile.close()
    

    由于有时我们无法提前获取行数,所以我定义了一种缓冲来避免一直调整数组大小。

    注意:起初,我想出了一个使用numpy.append 的解决方案。但正如 cmets 中所指出的,append 也是低效的,因为它会复制数组内容。

    【讨论】:

    • 如果这确实是问题,一种解决方案是读取和解析块中的文件,然后连接 numpy 数组。但是,如果有更高效的内存实现可用,那将是可取的;我不会感到惊讶。 OTOH,也许您首先需要在寻找文本文件的效率时退后一步......
    • 我对 numpy 了解不多,但append 不是总是创建一个添加了参数的副本吗?如果是这样,这段代码可能没什么用:它需要 O(n²) 时间而不是 O(n) 时间(对于 n = 8e6 来说是一个严重的问题),并且可能仍然会使所有临时文件的内存消耗翻倍。
    • 你是对的@delnan。我在内部实现中使用resize 函数。但认为使用 np.append 会是一个更简单的解决方案。
    • @itghisi: 非常感谢numpy.loadtxt 确实是罪魁祸首,受你提出的启发我编写了一个替代方案,我将在上面分享(不久我更喜欢计算行数而不是使用缓冲区和调整大小,最后留下空行)。
    • 很好@Learningisamess。我认为没有平台无关的解决方案来计算行数,所以我现在将保留缓冲/调整大小的答案。很高兴我能提供帮助。
    猜你喜欢
    • 2013-03-28
    • 2017-12-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-12-18
    • 2021-12-31
    • 2021-06-04
    • 2020-08-10
    相关资源
    最近更新 更多