web-dev-qa-db-ja.com

非常に大きい(> 20GB)テキストファイルを1行ずつ処理する

処理する必要がある非常に大きなテキストファイルがいくつかありますが、最大のものは約60GBです。

各行には7つのフィールドに54文字が含まれており、最初の3つのフィールドのそれぞれから最後の3文字を削除します。これにより、ファイルサイズが約20%削減されます。

私はPythonが初めてであり、1時間あたり約3.4 GBでやりたいことを実行するコードを持っていますが、価値のある運動であるためには、少なくとも10 GBを取得する必要があります。/hr-これを高速化する方法はありますか?このコードは私のプロセッサに挑戦することに近づいていないので、内部ハードドライブへの読み取りと書き込みの速度によって制限されていると推測します。

def ProcessLargeTextFile():
    r = open("filepath", "r")
    w = open("filepath", "w")
    l = r.readline()
    while l:
        x = l.split(' ')[0]
        y = l.split(' ')[1]
        z = l.split(' ')[2]
        w.write(l.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3]))
        l = r.readline()
    r.close()
    w.close()

どんな助けも本当に感謝されます。私はWindows 7でIDLE Python GUIを使用しており、16GBのメモリがあります-別のOSの方が効率的でしょうか?.

編集:処理するファイルの抜粋です。

70700.642014 31207.277115 -0.054123 -1585 255 255 255
70512.301468 31227.990799 -0.255600 -1655 155 158 158
70515.727097 31223.828659 -0.066727 -1734 191 187 180
70566.756699 31217.065598 -0.205673 -1727 254 255 255
70566.695938 31218.030807 -0.047928 -1689 249 251 249
70536.117874 31227.837662 -0.033096 -1548 251 252 252
70536.773270 31212.970322 -0.115891 -1434 155 158 163
70533.530777 31215.270828 -0.154770 -1550 148 152 156
70533.555923 31215.341599 -0.138809 -1480 150 154 158
40
Tom_b

このようなコードを書くのがより慣用的です

def ProcessLargeTextFile():
    with open("filepath", "r") as r, open("outfilepath", "w") as w:
        for line in r:
            x, y, z = line.split(' ')[:3]
            w.write(line.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3]))

ここでの主な節約は、splitを1回だけ実行することですが、CPUに負荷がかかっていない場合は、ほとんど違いはありません

It may一度に数千行を節約し、1回のヒットで書き込むことで、ハードドライブのスラッシングを減らすことができます。 100万行はのみ 54MBのRAMです!

def ProcessLargeTextFile():
    bunchsize = 1000000     # Experiment with different sizes
    bunch = []
    with open("filepath", "r") as r, open("outfilepath", "w") as w:
        for line in r:
            x, y, z = line.split(' ')[:3]
            bunch.append(line.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3]))
            if len(bunch) == bunchsize:
                w.writelines(bunch)
                bunch = []
        w.writelines(bunch)

@Janneが提案した、行を生成する別の方法

def ProcessLargeTextFile():
    bunchsize = 1000000     # Experiment with different sizes
    bunch = []
    with open("filepath", "r") as r, open("outfilepath", "w") as w:
        for line in r:
            x, y, z, rest = line.split(' ', 3)
            bunch.append(' '.join((x[:-3], y[:-3], z[:-3], rest)))
            if len(bunch) == bunchsize:
                w.writelines(bunch)
                bunch = []
        w.writelines(bunch)
26
John La Rooy

測定してください!あなたはpythonコードを改善する方法についてかなり有用なヒントを得ましたが、私はそれらに同意します。しかし、まずあなたの本当の問題は何かを理解する必要があります。

  • コードから処理を削除します。データを読み書きして、速度を測定するだけです。ファイルの読み取りと書き込みが遅すぎる場合は、コードの問題ではありません。
  • 読み取りと書き込みがすでに遅い場合は、複数のディスクを使用してみてください。あなたは同時に読み書きをしています。同じディスクに?はいの場合は、別のディスクを使用して再試行してください。
  • 非同期ioライブラリ(Twisted?)も役立つ場合があります。

正確な問題を見つけた場合は、その問題の最適化を再度依頼してください。

12
Achim

この答えを追加して説明しますwhyバッファリングは理にかなっており、またもう1つの解決策を提供します

あなたは息をのむほど悪いパフォーマンスを得ています。この記事 高速化することは可能ですかpython IO? は、10ギガバイトの読み取りに3分近くかかることを示しています。順次書き込みは同じ速度です。そのため、30のファクターが欠落しており、パフォーマンスの目標は、可能なはずの10倍遅いままです。

ほぼ間違いなく、この種の格差はヘッドシークの数ディスクが行っていることにあります。ヘッドシークにはミリ秒かかります。 1回のシークは、数メガバイトの順次読み取り/書き込みに対応します。非常に高価です。同じディスクでコピー操作を行うには、入力と出力をシークする必要があります。前述したように、シークを減らす1つの方法は、ディスクに書き込む前に多くのメガバイトが読み取られるようにバッファリングすることです。 python ioシステムにこれを行うように説得できる場合は素晴らしい。そうでなければ、行を文字列配列に読み込んで処理し、50 mbの出力が準備できた後に書き込むことができる。シークは、データ転送自体に関して10%未満のパフォーマンスヒットを引き起こします。

入力ファイルと出力ファイル間のシークを完全に排除するもう1つの非常に簡単な方法は、2台の物理ディスクを備えたマシンを使用し、それぞれのioチャネルを完全に分離することです。からの入力。他への出力。大量の大きなファイル変換を行う場合は、この機能を備えたマシンを用意しておくと便利です。

7
Gene

CPUではなくI/Oによって制限されているようには見えないので、openの3番目のパラメーターをいくつか変更してみましたか?

実際、この3番目のパラメーターを使用して、ファイル操作に使用するバッファーサイズを指定できます。

単純にopen( "filepath", "r", 16777216 )と書き込むと、ファイルから読み取るときに16 MBのバッファーが使用されます。役立つはずです。

出力ファイルにも同じものを使用し、残りは同じファイルで測定/比較します。

注:これは、他の人が推奨するのと同じ種類の最適化ですが、コードを変更することなく、自分でバッファリングすることなく、ここで無料で取得できます。

7
Didier Trosset
ProcessLargeTextFile():
    r = open("filepath", "r")
    w = open("filepath", "w")
    l = r.readline()
    while l:

すでに提案されているように、forループを使用してこれをより最適化することができます。

    x = l.split(' ')[0]
    y = l.split(' ')[1]
    z = l.split(' ')[2]

ここでは分割操作を3回実行していますが、各行のサイズに応じて、パフォーマンスに悪影響を及ぼします。一度分割して、戻ってくる配列のエントリにx、y、zを割り当てる必要があります。

    w.write(l.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3]))

読み取り中の各行は、すぐにファイルに書き込みます。これは、非常にI/O集中型です。出力をメモリにバッファリングし、定期的にディスクにプッシュすることを検討する必要があります。このようなもの:

BUFFER_SIZE_LINES = 1024 # Maximum number of lines to buffer in memory

def ProcessLargeTextFile():
    r = open("filepath", "r")
    w = open("filepath", "w")
    buf = ""
    bufLines = 0
    for lineIn in r:

        x, y, z = lineIn.split(' ')[:3]
        lineOut = lineIn.replace(x,x[:-3]).replace(y,y[:-3]).replace(z,z[:-3])
        bufLines+=1

        if bufLines >= BUFFER_SIZE:
            # Flush buffer to disk
            w.write(buf)
            buf = ""
            bufLines=1

        buf += lineOut + "\n"

    # Flush remaining buffer to disk
    w.write(buf)
    buf.close()
    r.close()
    w.close()

BUFFER_SIZEを調整して、メモリ使用量と速度の最適なバランスを決定できます。

4
seanhodges

あなたのコードはどちらかと言えば非正統的であり、必要以上に多くの関数呼び出しを行います。より簡単なバージョンは次のとおりです。

ProcessLargeTextFile():
    with open("filepath") as r, open("output") as w:
        for line in r:
            fields = line.split(' ')
            fields[0:2] = [fields[0][:-3], 
                           fields[1][:-3],
                           fields[2][:-3]]
            w.write(' '.join(fields))

そして私はslower Windowsよりも新しい現代のファイルシステムを知りません。これらの巨大なデータファイルをデータベースとして使用しているように見えるので、実際のデータベースの使用を検討しましたか?

最後に、ファイルサイズの縮小にのみ関心がある場合、ファイルの圧縮/圧縮を検討しましたか?

3
msw

これらは非常に大きなファイルのように見えます...なぜそんなに大きいのですか?行ごとにどのような処理をしていますか?いくつかのmap reduce呼び出し(適切な場合)またはデータの単純な操作でデータベースを使用しないのはなぜですか?データベースのポイントは、すべてがメモリに収まらない大量のデータの処理と管理を抽象化することです。

sqlite を使用すると、フラットファイルをデータベースとして使用するだけでアイデアを試すことができます。アイデアが便利だと思う場合は、 postgresql のようなもう少し堅牢で汎用性の高いものにアップグレードしてください。

データベースを作成する

 conn = sqlite3.connect('pts.db')
 c = conn.cursor()

テーブルを作成します

c.execute('''CREATE TABLE ptsdata (filename, line, x, y, z''')

次に、上記のアルゴリズムのいずれかを使用して、データベースにすべてのラインとポイントを挿入します。

c.execute("INSERT INTO ptsdata VALUES (filename, lineNumber, x, y, z)")

今、あなたはそれをどう使うかはあなたが何をしたいかに依存します。たとえば、クエリを実行してファイル内のすべてのポイントを操作するには

c.execute("SELECT lineNumber, x, y, z FROM ptsdata WHERE filename=file.txt ORDER BY lineNumber ASC")

そして、このクエリからn行を一度に取得します

c.fetchmany(size=n)

どこかでSQLステートメントのラッパーが優れていると思いますが、アイデアは得られます。

2
craastad

スペースを節約することだけをメリットとして挙げているので、gzipで圧縮したファイルを保存できない理由はありますか?このデータを70%以上節約できます。または、ランダムアクセスが依然として重要な場合は、NTFSでファイルを圧縮することを検討してください。これらのいずれかを実行すると、I/O時間を大幅に節約できます。

さらに重要なことは、3.4GB /時しか得られないデータはどこにあるのでしょうか?これは、USBv1の速度の低下です。

2
jthill

メモリの問題を引き起こすことなく、任意のサイズのテキストファイルを読み込むためのコードを以下に示します。ギガバイトサイズのファイルをサポートします。あらゆる種類のマシンでスムーズに実行されます。システムRAMに基づいてCHUNK_SIZEを設定するだけです。 CHUNK_SIZEが多ければ多いほど、一度に読み取られるデータも多くなります。

https://Gist.github.com/iyvinjose/e6c1cb2821abd5f01fd1b9065cbc759d

ファイルdata_loading_utils.pyをダウンロードして、コードにインポートします

使用法

import data_loading_utils.py.py
file_name = 'file_name.ext'
CHUNK_SIZE = 1000000


def process_lines(line, eof, file_name):

    # check if end of file reached
    if not eof:
         # process data, data is one single line of the file

    else:
         # end of file reached

data_loading_utils.read_lines_from_file_as_data_chunks(file_name, chunk_size=CHUNK_SIZE, callback=process_lines)

process_linesメソッドはコールバック関数です。すべての行に対して呼び出され、パラメータ行は一度にファイルの1つの行を表します。

マシンのハードウェア構成に応じて、変数CHUNK_SIZEを構成できます。

1
Iyvin Jose

最初に分割結果を保存し、フィールドが必要になるたびに保存しないようにすることができます。これがスピードアップするかもしれません。

gUIで実行しないようにすることもできます。 cmdで実行します。

1
Muetze

for l in r:を使用してファイルを読み取り、バッファリングの恩恵を受けます。

1
Janne Karila