我在配备1GB RAM的Mac Mini上使用Python 2.6。我想阅读一个巨大的文本文件
$ 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
我得到的输出是:
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文件的25%之后,Python消耗了500MB。因此,似乎将文件的内容存储为整数元组的列表并不是非常有效的内存。 有没有更好的方法来做到这一点,以便我可以将我的500MB文件读入我的1GB内存?
答案 0 :(得分:19)
有一个用于排序大于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上进行排序。 ,我使用的唯一方法是将列排在一起,而不是默认情况下对它们进行排序(完全弄乱你的数据)。
答案 1 :(得分:7)
所有python对象在它们实际存储的数据之上都有内存开销。根据我的32位Ubuntu系统上的getsizeof,元组的开销为32字节,int占用12个字节,因此文件中的每一行占用56个字节+列表中的4个字节指针 - 我认为它会很多更多的64位系统。这与您给出的数字一致,意味着您的3000万行将占用1.8 GB。
我建议您使用unix排序实用程序而不是使用python。我不是Mac头,但我认为OS X排序选项与linux版本相同,所以这应该有效:
sort -n -t, -k2 links.csv
-n表示按数字排序
-t,表示使用逗号作为字段分隔符
-k2表示对第二个字段进行排序
这将对文件进行排序并将结果写入stdout。您可以将其重定向到另一个文件或将其传递给您的python程序以进行进一步处理。
编辑: 如果您不想在运行python脚本之前对文件进行排序,则可以使用子进程模块创建到shell排序实用程序的管道,然后从管道输出中读取排序结果。
答案 2 :(得分:4)
由于这些都只是数字,因此将它们加载到Nx2数组中会消除一些开销。将NumPy用于多维数组。或者,您可以使用两个普通的python arrays来表示每列。
答案 3 :(得分: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内存的成本是多少?
答案 4 :(得分:2)
您可能需要查看mmap:
http://docs.python.org/library/mmap.html
它可以让你像处理一个大数组/字符串一样处理文件,并让操作系统处理数据进出内存以使其适应。
所以你可以在csv文件中读取,一次一行,然后将结果写入mmap'd文件(以合适的二进制格式),然后处理mmap文件。由于mmap的文件只是临时的,你当然可以为此目的创建一个tmp文件。
这里有一些代码使用mmap和tempfile来读取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等)。希望它能给你一个起点。
答案 5 :(得分:0)
我使用external merge sort为此用例创建了一个模块: https://bitbucket.org/richardpenman/csvsort
>>> from csvsort import csvsort
>>> csvsort('links.csv', columns=[1], has_header=False)