2009-12-13 60 views
21

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 

ファイル内の各行は、2つのコンマで区切られた整数値のタプルで構成されています。 ファイル全体を読み込み、2番目の列に従ってソートします。私は、ファイル全体をメモリに読み込まずに並べ替えを実行できることを知っています。しかし、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%だけを読んだ後、パイソン500MBを消費します。だから、ファイルの内容をintのタプルのリストとして格納することは、あまりメモリ効率的ではないようです。 私の500MBファイルを1GBのメモリに読み込めるように、より良い方法がありますか?

+0

: – Drakosha

+0

生データに基づいてメモリの見積もりを作成しますが、タプルとintを作成します。短い文字列と比較すると、Pythonのインスタンスのオーバーヘッドは目に見えるように目立っています。あなたは純粋な文字列としてこのデータをソートすることができます、あなたはそれを試してみましたか? – u0b34a0f6ae

+0

私のメモリ見積もりでは、int、タプル、およびリストのメモリ消費量が加算されます。それはかなり大丈夫です、私はtopを使って見るものとほぼ同じです(Pythonインタプリタで消費されるメモリを引いたもの)。しかし、私は純粋な文字列としてデータをソートしようとしていません。どうすればいい? – asmaier

答えて

18

RAM on this pageよりも大きなファイルをソートするためのレシピがありますが、CSV形式のデータが必要な場合にはそれに合わせる必要があります。追加リソースへのリンクもあります。

編集:真、ディスク上のファイルは、「RAMより大きい」ではありませんが、メモリ内の表現は簡単利用可能なRAMよりもはるかに大きくなることができます。 1つのこととして、あなた自身のプログラムは1GB(OSオーバーヘッドなど)全体を取得しません。別の方法として、これを純粋なPython(32ビットマシンなどと仮定した2つの整数リスト)の最もコンパクトな形式で保存しても、それらの30Mの整数対に対して934MBを使用します。

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

+0

@asmaier、メモリ使用量を明確にした編集済みの解答、およびnumpyを使用した解決策があります。 –

+0

解決策に2つの質問:アレイを事前に割り当てる必要があるのはなぜですか? numpy.fromfile()を使って配列を生成することはできませんでしたか? – asmaier

4

これらはすべて単なる数値なので、それらをNx2配列にロードすると、オーバーヘッドがいくらか除去されます。多次元配列にはNumPyを使用します。あるいは、2つの通常の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ビットシステムでもっとこれはあなたが与えた数値と一致しており、3千万行には1.8 GBがかかります。

私は、Pythonを使用する代わりに、unix sortユーティリティを使用することをお勧めします。私は、Mac-頭ではないが、私はOS Xの並べ替えオプションは、Linux版と同じなので、これは動作するはず推測:

sort -n -t, -k2 links.csv 

-nは、ソート数値

-tを意味することは、カンマを使用することを意味しますフィールドセパレータ

-k2がソート2番目のフィールド上の

を意味し、これは、ファイルをソートし、標準出力に結果を書き込みます。他のファイルにリダイレクトするか、pythonプログラムにパイプして、さらに処理してください。

編集: pythonスクリプトを実行する前にファイルをソートしたくない場合は、サブプロセスモジュールを使用してシェルソートユーティリティへのパイプを作成し、パイプの出力からソート結果を読み取ることができます。

+0

Windowsユーザーにとっては、GnuWin32プロジェクトの互換性のあるsort.exeをhttp://gnuwin32.sourceforge.net/ –

+0

から入手できます。ソリューションをソートするだけで間違いなく最も高速です。私の場合、 'sort'はデータをソートしてファイルに出力するのに450秒必要でしたが、pythonの解決策は1750年を必要としました(そしてほとんどの時間はファイルを書きます)。しかし、 'sort 'は440MBのRAMを使いましたが、Peter Hansenが提案したPythonソリューションは240MBしか必要としませんでした。そして、両方のソリューションは私のデュアルコアマシンのコアを1つしか使用していなかったので、まだ改善の余地が残っています... – asmaier

2

あなたはmmapを見たいかもしれません:

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

それはあなたが大きな配列/文字列のようなファイルを扱うもらおうとし、メモリのうちににOSがシャッフルデータを扱えるようになるだろうフィットさせてください。

したがって、csvファイルを一度に1行読み込み、結果をmmap'dファイル(適切なバイナリ形式)に書き込んだり、mmap'dファイルで作業したりできます。 mmapされたファイルは一時的なものなので、もちろんこの目的のためにtmpファイルを作成することもできます。ここで

はデモがCSVデータを読み込み、ペアの整数のようにそれを保存するために一時ファイルにはmmapを使用していくつかのコードです:


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などで)リストのように動作するようにアクセスできます。うまくいけば、それはあなたに出発点を与えるだろうと思った。私は、Pythonのように、uは本当にメモリが起こっている場所を知ることができない、通訳と推測 https://bitbucket.org/richardpenman/csvsort

>>> from csvsort import csvsort 
>>> csvsort('links.csv', columns=[1], has_header=False) 
+1

(1)ソートはどこですか? (2)array.arrayメソッドの代わりにstruct.packとstruct.unpackを使用することを検討してください。 - はるかに少ないオーバーヘッド(開始のために1つの関数呼び出しで2つの値を行います) (3)tuple()の必要はありません )は、slpit後に両方の部分を削除する必要があります –

0

私は external merge sortを使用して、このユースケースのためのモジュールを作成しました。しかし、リスト(通常は - 私は正確なPythonの実装を知らない)は、配列よりも多くのメモリを必要とします(例えば、前/次のポインタの場合)。 おそらくC/C++を使用して、どれだけのメモリを使用しているかを知る必要があります。
関連する問題