web-dev-qa-db-ja.com

Python:巨大なテキストファイルをメモリに読み込む方法

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

したがって、ファイルの各行は、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%のみを読み取った後、Pythonは500MBを消費します。そのため、ファイルのコンテンツをintのタプルのリストとして保存することは、あまりメモリ効率が良くないようです。 500MBのファイルを1GBのメモリに読み込むことができるようにするためのより良い方法?

25
asmaier

RAM このページ )よりも大きいファイルをソートするためのレシピがありますが、CSV形式のデータが関係するケースに合わせて調整する必要があります。そこにある追加のリソースへのリンク。

Edit:True、ディスク上のファイルは「RAMより大きい」わけではありませんが、メモリ内の表現はよりもはるかに大きくなる可能性があります使用可能なRAM。一つには、あなた自身のプログラムは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"を取ることができますが、これらはどちらも レコード配列 でソートできないようです。 =ちなみに、列を並べ替える唯一の方法として使用したtogether個別に並べ替えるデフォルト(データを完全に乱雑にする)とは対照的です。

20
Peter Hansen

すべてのpythonオブジェクトには、実際に格納しているデータに加えてメモリオーバーヘッドがあります。32ビットUbuntuシステムのgetsizeofによると、タプルには32バイトのオーバーヘッドがあり、intには12バイト、したがって、ファイルの各行は56バイト+リストの4バイトのポインタを必要とします-これは64ビットシステムの場合ははるかに多くなると思います。これは、指定した数値と一致しており、3,000万行に1.8 GB。

pythonを使用する代わりに、UNIXの並べ替えユーティリティを使用することをお勧めします。私はMacの頭ではありませんが、OS Xの並べ替えオプションはLinuxのバージョンと同じであるため、これは機能するはずです。

sort -n -t, -k2 links.csv

-nは数値でソートすることを意味します

-tは、フィールド区切り文字としてカンマを使用することを意味します

-k2は、2番目のフィールドでのソートを意味します

これにより、ファイルがソートされ、結果がstdoutに書き込まれます。これを別のファイルにリダイレクトするか、パイプして、pythonプログラムを実行して、さらに処理を行うことができます。

編集:pythonスクリプトを実行する前にファイルを並べ替えたくない場合は、サブプロセスモジュールを使用してシェルの並べ替えユーティリティへのパイプを作成し、並べ替えられた結果をパイプの出力。

8
Dave Kirby

これらはすべて単なる数値であるため、Nx2配列にロードすると、オーバーヘッドがいくらか取り除かれます。多次元配列にはNumPyを使用します。または、2つの通常のpython arrays を使用して各列を表すこともできます。

4
kwatford

入力行をメモリに格納する最も安価な方法は、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
John Machin

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

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

ファイルを大きな配列/文字列のように扱うことができ、OSがメモリにデータを入れたり出してデータを入れ替えたりして、収まるようにします。

したがって、csvファイルを一度に1行ずつ読み取ってから、結果をmmapしたファイル(適切なバイナリ形式)に書き込んでから、mmapしたファイルで作業できます。 mmapされたファイルは一時的なものに過ぎないので、もちろんこの目的のために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)

それは少し荒いです。実際には、それらすべてをNiceクラスでラップし、リストのように動作するようにEdgeにアクセスできるようにする必要があります(インデックス付け、lenなどを使用)。うまくいけば、それが出発点になると思いました。

2
John Montgomery

external merge sort を使用してこのユースケースのモジュールを作成しました: https://bitbucket.org/richardpenman/csvsort

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