2009-12-13 57 views
21

我在1GB RAM的Mac Mini上使用Python 2.6。我想读一个巨大的文本文件Python:如何将巨大的文本文件读入内存

$ ls -l links.csv; file links.csv; tail links.csv 
-rw-r--r-- 1 user user 469904280 30 Nov 22:42 links.csv 
links.csv: ASCII text, with CRLF line terminators 
4757187,59883 
4757187,99822 
4757187,66546 
4757187,638452 
4757187,4627959 
4757187,312826 
4757187,6143 
4757187,6141 
4757187,3081726 
4757187,58197 

因此,文件中的每一行由一个由两个以逗号分隔的整数值的元组构成。 我想阅读整个文件并根据第二列进行排序。我知道,我可以在不将整个文件读入内存的情况下进行排序。但我认为一个500MB的文件,我应该仍然可以在内存中执行它,因为我有1GB可用。

但是,当我尝试读取文件时,Python似乎分配了比磁盘上的文件所需更多的内存。所以即使使用1GB的RAM,我也无法将500MB文件读入内存。 我的文件进行读取和打印一些信息有关的内存消耗Python代码是:

#!/usr/bin/python 
# -*- coding: utf-8 -*- 

import sys 

infile=open("links.csv", "r") 

edges=[] 
count=0 
#count the total number of lines in the file 
for line in infile: 
count=count+1 

total=count 
print "Total number of lines: ",total 

infile.seek(0) 
count=0 
for line in infile: 
edge=tuple(map(int,line.strip().split(","))) 
edges.append(edge) 
count=count+1 
# for every million lines print memory consumption 
if count%1000000==0: 
    print "Position: ", edge 
    print "Read ",float(count)/float(total)*100,"%." 
    mem=sys.getsizeof(edges) 
    for edge in edges: 
    mem=mem+sys.getsizeof(edge) 
    for node in edge: 
    mem=mem+sys.getsizeof(node) 

    print "Memory (Bytes): ", mem 

我得到的输出是:

阅读只有25%的500MB的文件,巨蟒后
Total number of lines: 30609720 
Position: (9745, 2994) 
Read 3.26693612356 %. 
Memory (Bytes): 64348736 
Position: (38857, 103574) 
Read 6.53387224712 %. 
Memory (Bytes): 128816320 
Position: (83609, 63498) 
Read 9.80080837067 %. 
Memory (Bytes): 192553000 
Position: (139692, 1078610) 
Read 13.0677444942 %. 
Memory (Bytes): 257873392 
Position: (205067, 153705) 
Read 16.3346806178 %. 
Memory (Bytes): 320107588 
Position: (283371, 253064) 
Read 19.6016167413 %. 
Memory (Bytes): 385448716 
Position: (354601, 377328) 
Read 22.8685528649 %. 
Memory (Bytes): 448629828 
Position: (441109, 3024112) 
Read 26.1354889885 %. 
Memory (Bytes): 512208580 

已消耗500MB。因此,似乎将文件的内容存储为int整数列表并不是非常有效的内存。 有没有更好的方法来做到这一点,以便我可以将我的500MB文件读入我的1GB内存中?

+0

我想与解释,像Python,你无法真正知道哪里是内存下去。然而,列表[通常 - 我不知道确切的python实现)需要比数组更多的内存,例如prev/next指针。 您可能需要使用C/C++来确切知道您使用了多少内存。 – Drakosha 2009-12-13 14:41:28

+0

您将您的内存估计基于原始数据,但随后创建元组和整数。您可以看到,与短字符串相比,Python的实例开销在此处显而易见。您甚至可以将这些数据作为纯字符串进行排序,您是否尝试过? – u0b34a0f6ae 2009-12-13 14:42:30

+0

我的记忆估计增加了整数,元组和列表的内存消耗。这是相当好的,它大致相同(减去Python解释器所消耗的内存),正如我在top中看到的那样。但我没有试图将数据作为纯字符串进行排序。我会怎么做? – asmaier 2009-12-13 14:57:14

回答

18

有一种排序文件大于RAM on this page的方法,但您必须根据涉及CSV格式数据的情况对其进行调整。这里还有其他资源的链接。

编辑:真,磁盘上的文件是不是“比RAM大”,但在内存中的表示很容易成为比可用RAM大得多。首先,你自己的程序不会得到整个1GB(操作系统开销等)。另一方面,即使您以纯Python的最紧凑形式(两个整数列表,假设32位机器等)存储它,您将使用934MB来处理这30M对整数。

使用numpy你也可以完成这项工作,只需要大约250MB。这是不特定的加载速度快,这样一来,你一定要算上线和预分配数组,但它可能是最快的实际排序因为它的内存:

import time 
import numpy as np 
import csv 

start = time.time() 
def elapsed(): 
    return time.time() - start 

# count data rows, to preallocate array 
f = open('links.csv', 'rb') 
def count(f): 
    while 1: 
     block = f.read(65536) 
     if not block: 
      break 
     yield block.count(',') 

linecount = sum(count(f)) 
print '\n%.3fs: file has %s rows' % (elapsed(), linecount) 

# pre-allocate array and load data into array 
m = np.zeros(linecount, dtype=[('a', np.uint32), ('b', np.uint32)]) 
f.seek(0) 
f = csv.reader(open('links.csv', 'rb')) 
for i, row in enumerate(f): 
    m[i] = int(row[0]), int(row[1]) 

print '%.3fs: loaded' % elapsed() 
# sort in-place 
m.sort(order='b') 

print '%.3fs: sorted' % elapsed() 

输出上我机器的样本文件类似于您显示的样本:

6.139s: file has 33253213 lines 
238.130s: read into memory 
517.669s: sorted 

numpy的默认值为Quicksort。 ndarray.sort()例程(就地排序)也可以采用关键字参数kind="mergesort"kind="heapsort",但它看起来都不能在Record Array上排序,顺便说一句,我用它作为唯一可以看到的排序方式列在一起而不是默认,它们将独立排序(完全搞乱你的数据)。

+0

但我的问题是关于排序比内存中的可用RAM更小的文件。 – asmaier 2009-12-13 14:50:30

+0

@asmaier,请参阅已编辑的答案,澄清内存使用情况,以及使用可能适合您的numpy的解决方案。 – 2009-12-13 17:10:57

+0

解决方案的两个问题:为什么需要预先分配数组?难道不能简单地使用numpy.fromfile()来生成数组吗? – asmaier 2009-12-13 19:46:07

4

由于这些都是数字,因此将它们加载到Nx2数组中会消除一些开销。将NumPy用于多维数组。另外,你可以使用两个普通的python arrays来表示每一列。

4

将输入行存储在内存中的最便宜的方法是array.array('i')元素 - 假设每个数字都符合带符号的32位整数。内存成本将是8N字节,其中N是行数。

这里是怎么做的排序和写在有序输出文件:

from array import array 
import csv 
a = array('i') 
b = array('i') 
for anum, bnum in csv.reader(open('input.csv', 'rb')): 
    a.append(int(anum)) 
    b.append(int(bnum)) 
wtr = csv.writer(open('output.csv', 'wb')) 
for i in sorted(xrange(len(a)), key=lambda x: b[x]): 
    wtr.writerow([a[i], b[i]]) 

不幸的是sorted()返回一个列表,而不是一个迭代,而这个名单将是相当大的:为指针和4N字节int对象的12N字节,即sorted()输出的16N字节。注意:这是基于32位机器上的CPython 2.X;每个3.X和64位机器都会变得更糟。所有这些都是24N字节。你有3100万行,所以你需要31 * 24 = 744 MB ...看起来应该工作;请注意,此计算不允许按排序分配任何内存,但您具有合理的安全余量。

另外:在工资率上以小时表示的额外GB或3内存的成本是多少?

7

所有python对象在实际存储的数据之上都有内存开销。根据我的32位Ubuntu系统上的getsizeof,一个元组的开销为32字节,而一个int需要12个字节,因此文件中的每一行都需要56字节+列表中的4字节指针 - 我认为它会很多更多用于64位系统。这与您给出的数字一致,意味着您的3000万行将占用1.8 GB。

我建议您不要使用python,而要使用unix sort工具。我不是Mac的头,但我相信OS X的排序选项是相同的Linux版本,所以这应该工作:

sort -n -t, -k2 links.csv 

-n表示排序数值

-t,是指使用逗号作为字段分隔符

-k2意味着在第二场

这将排序的文件并将结果写入到stdout排序。你可以将它重定向到另一个文件或者将它传送给你的python程序做进一步的处理。

编辑: 如果您不想在运行python脚本之前对文件进行排序,则可以使用subprocess模块​​创建一个到shell排序实用程序的管道,然后从管道输出中读取排序结果。

+0

为了Windows用户的利益:您可以从http://gnuwin32.sourceforge.net/ – 2009-12-13 21:29:22

+0

的GnuWin32项目中获得兼容的sort.exe。只是为了排序解决方案绝对是最快的。在我的情况'排序'需要450秒排序和输出我的数据到一个文件,而python解决方案需要1750年(并花费大部分时间只是写文件)。然而'sort'使用了440MB的RAM,而Peter Hansen提出的python解决方案只需要240MB。而且这两种解决方案只使用了我的双核机器的一个核心,所以仍然有很大的改进空间。 – asmaier 2009-12-15 23:39:07

2

你可能想看看MMAP:

http://docs.python.org/library/mmap.html

它会告诉你如何对待像一个大数组/字符串的文件,将得到的OS来处理洗牌的数据流入和流出的内存让它合适。

所以你可以在csv文件中读取一行,然后将结果写入mmap'd文件(以合适的二进制格式),然后在mmap'd文件上工作。由于mmap'd文件只是暂时的,你当然可以为此创建一个tmp文件。

下面是一些代码,使用mmap与临时文件的演示,以CSV数据读取并存储为整数的对的:


import sys 
import mmap 
import array 
from tempfile import TemporaryFile 

def write_int(buffer, i): 
    # convert i to 4 bytes and write into buffer 
    buffer.write(array.array('i', [i]).tostring()) 

def read_int(buffer, pos): 
    # get the 4 bytes at pos and convert to integer 
    offset = 4*pos 
    return array.array('i', buffer[offset:offset+4])[0] 

def get_edge(edges, lineno): 
    pos = lineno*2 
    i, j = read_int(edges, pos), read_int(edges, pos+1) 
    return i, j 

infile=open("links.csv", "r") 

count=0 
#count the total number of lines in the file 
for line in infile: 
    count=count+1 

total=count 
print "Total number of lines: ",total 

infile.seek(0) 

# make mmap'd file that's long enough to contain all data 
# assuming two integers (4 bytes) per line 
tmp = TemporaryFile() 
file_len = 2*4*count 
# increase tmp file size 
tmp.seek(file_len-1) 
tmp.write(' ') 
tmp.seek(0) 
edges = mmap.mmap(tmp.fileno(), file_len) 

for line in infile: 
    i, j=tuple(map(int,line.strip().split(","))) 
    write_int(edges, i) 
    write_int(edges, j) 

# now confirm we can read the ints back out ok 
for i in xrange(count): 
    print get_edge(edges, i) 

这是一个有点粗糙,但。真的,你可能想用一个不错的类来包装所有的东西,这样你的边就可以以一种方式被访问,使它们像一个列表(带有索引,len等)。希望它能给你一个出发点。

+1

(1)哪里有一个排序的位? (2)考虑使用struct.pack和struct.unpack而不是array.array方法 - 少得多的开销(在一个函数调用中做2个值,用于开始) (3)不需要元组() (4) )应该剥离两个部件之后 – 2009-12-13 21:18:50