为什么加载这个文件会占用如此多的内存?

5
尝试将文件加载到Python中。这是一个非常大的文件(1.5Gb),但我有可用的内存,我只想执行一次(因此使用Python,我只需要对文件进行一次排序,所以Python是一个简单的选择)。
我的问题是加载此文件会导致内存使用过多。当我将大约10%的行加载到内存中时,Python已经使用了700Mb,这显然太多了。在大约50%时,脚本挂起,使用3.03 Gb的实际内存(并且慢慢上升)。
我知道这不是最有效的文件排序方法(就内存而言),但我只想让它工作,这样我就可以转向更重要的问题:D。那么,以下Python代码有什么问题会导致内存使用量巨大:
print 'Loading file into memory'
input_file = open(input_file_name, 'r')
input_file.readline() # Toss out the header
lines = []
totalLines = 31164015.0
currentLine = 0.0
printEvery100000 = 0
for line in input_file:
    currentLine += 1.0
    lined = line.split('\t')
    printEvery100000 += 1
    if printEvery100000 == 100000:
        print str(currentLine / totalLines)
        printEvery100000 = 0;
    lines.append( (lined[timestamp_pos].strip(), lined[personID_pos].strip(), lined[x_pos].strip(), lined[y_pos].strip()) )
input_file.close()
print 'Done loading file into memory'

编辑:如果有人不确定,普遍的共识似乎是每个分配的变量都会占用更多的内存。在这种情况下,我通过以下方式“修复”了它:1)调用readLines(),它仍然加载所有数据,但每行只有一个“字符串”变量开销。这使用了大约1.7GB的内存来加载整个文件。然后,当我调用lines.sort()时,我传递一个函数到key上,该函数在制表符处拆分并返回正确的列值,转换为int类型。这在计算上很慢,总体上占用了大量内存,但它确实有效。今天我学到了很多关于变量分配开销的知识:D


好的,但是我们正在谈论比我预期消耗的内存多5倍。我不认为它们占用了那么多额外的内存! - Hamy
我不喜欢 :/ 我的直觉是使用for循环读取的行没有被释放内存,但在input_file上调用flush会抛出错误(例如,我似乎无法强制只读文件清理其stdio内存) - Hamy
2
内存使用量比我预期的要高。我会将数据导入数据库,运行带有预期ORDER BY子句的查询,并将结果写入磁盘。你使用的是哪个版本的Python? - Steven Rumbalski
2
开销比你想象的要高。我在这里快速分析了PHP的变量内存使用情况链接,我预计Python(以及Perl和Ruby等)会产生类似的结果。 - mu is too short
木头,看起来你先找到了答案 - 如果你发布它,我会接受它。 - Hamy
显示剩余6条评论
2个回答

3
这是一个根据您的示例推导出的常量估算所需内存的粗略值。最少需要计算每个拆分行的Python内部对象开销,以及每个字符串的开销。
假设以下常量(稍有偏差),它们只使用每行的一部分,估计需要9.1 GB的内存来存储文件:
- 文件大小为1.5 GB - 总共有31,164,015行 - 每行分成4个部分的列表
代码:
import sys
def sizeof(lst):
    return sys.getsizeof(lst) + sum(sys.getsizeof(v) for v in lst)

GIG = 1024**3
file_size = 1.5 * GIG
lines = 31164015
num_cols = 4
avg_line_len = int(file_size / float(lines))

val = 'a' * (avg_line_len / num_cols)
lst = [val] * num_cols

line_size = sizeof(lst)
print 'avg line size: %d bytes' % line_size
print 'approx. memory needed: %.1f GB' % ((line_size * lines) / float(GIG))

返回:

avg line size: 312 bytes
approx. memory needed: 9.1 GB

非常有趣,尽管我相当有信心这个数学不正确,即使最终的答案(和观点)需要更多的存储空间是正确的。我怀疑的原因是我可以调用readlines()并加载整个文件,使用约1.5GB的内存模型,这似乎是您要使用的内存模型(基于sizeof的定义),例如一堆包含若干字符的行对象(字符串)。 - Hamy
编辑:使用readLines()加载需要约1.7GB,因此似乎罪魁祸首是变量分配。 - Hamy
1
9.1GB的估计是一个高水位标记,假设您使用每行的100%(您正在进行一些拆分和索引单个字段,因此我不知道结果字段长度)。因此,如果您平均只使用每行的2/3,则会使用约6GB,这与您在50%观察下的3GB相吻合。 - samplebias

1

我不知道关于内存使用分析的情况,但您可以尝试以下方法,以便在不耗尽内存的情况下使其正常工作。您将对一个新文件进行排序,该文件使用内存映射进行访问(据我所知,这将在内存方面高效运行)。Mmap具有一些特定于操作系统的工作方式,我在Linux上进行了测试(规模非常小)。

这是基本代码,为了使其具有良好的时间效率,您可能需要在已排序的文件上执行二进制搜索,以查找要插入行的位置,否则可能需要很长时间。

您可以在此问题中找到一个寻找文件的二进制搜索算法。

希望这是一种内存高效的按行对大型文件进行排序的方法:

import os
from mmap import mmap

input_file = open('unsorted.txt', 'r')
output_file = open('sorted.txt', 'w+')

# need to provide something in order to be able to mmap the file
# so we'll just copy the first line over
output_file.write(input_file.readline())
output_file.flush()
mm = mmap(output_file.fileno(), os.stat(output_file.name).st_size)
cur_size = mm.size()

for line in input_file:
  mm.seek(0)
  tup = line.split("\t")
  while True:
    cur_loc = mm.tell()
    o_line = mm.readline()
    o_tup = o_line.split("\t")
    if o_line == '' or tup[0] < o_tup[0]: # EOF or we found our spot
      mm.resize(cur_size + len(line))
      mm[cur_loc+len(line):] = mm[cur_loc:cur_size]
      mm[cur_loc:cur_loc+len(line)] = line
      cur_size += len(line)
      break

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接