23

読み込んで辞書を作成する必要がある大きなファイルがあります。私はこれをできるだけ速くしたいと思っています。しかし、Python での私のコードは遅すぎます。問題を示す最小限の例を次に示します。

最初にいくつかの偽のデータを作成します

paste <(seq 20000000) <(seq 2 20000001)  > largefile.txt

これは、それを読み込んで辞書を作成するための最小限の Python コードです。

import sys
from collections import defaultdict
fin = open(sys.argv[1])

dict = defaultdict(list)

for line in fin:
    parts = line.split()
    dict[parts[0]].append(parts[1])

タイミング:

time ./read.py largefile.txt
real    0m55.746s

ただし、次のようにファイル全体をより高速に読み取ることができます。

time cut -f1 largefile.txt > /dev/null    
real    0m1.702s

私のCPUには8つのコアがあります.Pythonでこのプログラムを並列化して高速化することはできますか?

1 つの可能性としては、入力の大きなチャンクを読み取り、重複しない異なるサブチャンクで 8 つのプロセスを並行して実行し、メモリ内のデータから並列に辞書を作成してから、別の大きなチャンクを読み取ることです。これは何とかマルチプロセッシングを使用してPythonで可能ですか?

更新します。キーごとに値が 1 つしかなかったため、偽のデータはあまり良くありませんでした。ベターは

perl -E 'say int rand 1e7, $", int rand 1e4 for 1 .. 1e7' > largefile.txt

(大きなファイルを読み込んで辞書を作成する に関連します。)

4

6 に答える 6

6

これを並列化して高速化することは可能かもしれませんが、複数の読み取りを並行して行うことは役に立たないでしょう。

お使いの OS が、複数の読み取りを並行して有効に実行する可能性は低いです (例外は、ストライプ化された RAID アレイのようなものです。この場合、それを最適に使用するにはストライドを知る必要があります)。

あなたができることは、比較的高価な文字列/辞書/リスト操作を読み取りと並行して実行することです。

したがって、1 つのスレッドが (大きな) チャンクを読み取って同期キューにプッシュし、1 つ以上のコンシューマー スレッドがキューからチャンクをプルして行に分割し、ディクショナリに入力します。

(Pappnese が言うように、複数のコンシューマー スレッドを使用する場合は、スレッドごとに 1 つの辞書を作成してから結合します)。


ヒント:


再。報奨金:

C には明らかに競合する GIL がありません。ただし、読み取り動作は変わりません。欠点は、C にはハッシュ マップ (まだ Python スタイルの辞書が必要な場合) と同期キューの組み込みサポートがないため、適切なコンポーネントを見つけるか、独自のコンポーネントを作成する必要があることです。複数のコンシューマーがそれぞれ独自の辞書を作成し、最後にそれらをマージするという基本的な戦略は、おそらく最良の方法です。

strtok_rの代わりに使用str.splitする方が速いかもしれませんが、すべての文字列のメモリも手動で管理する必要があることに注意してください。ああ、行の断片を管理するためのロジックも必要です。正直なところ、C には非常に多くのオプションが用意されているので、プロファイルを作成して確認するだけでよいと思います。

于 2013-08-07T13:29:02.157 に答える
3

処理プールを使用するとこのような問題を解決できると考えたくなるかもしれませんが、少なくとも純粋な Python では、それよりもかなり複雑になるでしょう。

OPは、各入力行のリストは実際には2つの要素よりも長くなると述べたので、次を使用して少し現実的な入力ファイルを作成しました。

paste <(seq 20000000) <(seq 2 20000001) <(seq 3 20000002) |
  head -1000000 > largefile.txt

元のコードをプロファイリングした後、プロセスの最も遅い部分は行分割ルーチンであることがわかりました。(私のマシン.split()よりも約2倍長くかかりました。).append()

1000000    0.333    0.000    0.333    0.000 {method 'split' of 'str' objects}
1000000    0.154    0.000    0.154    0.000 {method 'append' of 'list' objects}

したがって、分割を別の関数に分解し、プールを使用してフィールドを分割する作業を分散させます。

import sys
import collections
import multiprocessing as mp

d = collections.defaultdict(list)

def split(l):
    return l.split()

pool = mp.Pool(processes=4)
for keys in pool.map(split, open(sys.argv[1])):
    d[keys[0]].append(keys[1:])

残念ながら、プールを追加すると、2 倍以上遅くなりました。元のバージョンは次のようになります。

$ time python process.py smallfile.txt 
real    0m7.170s
user    0m6.884s
sys     0m0.260s

対並列バージョン:

$ time python process-mp.py smallfile.txt 
real    0m16.655s
user    0m24.688s
sys     0m1.380s

呼び出しは.map()基本的に各入力をシリアライズ (pickle) し、それをリモート プロセスに送信してから、リモート プロセスからの戻り値をデシリアライズ (unpickle) する必要があるため、この方法でプールを使用すると非常に遅くなります。プールにコアを追加することである程度の改善が得られますが、これはこの作業を分散する根本的に間違った方法であると私は主張します.

コア間でこれを本当に高速化するには、ある種の固定ブロック サイズを使用して入力の大きなチャンクを読み取る必要があると思います。次に、ブロック全体をワーカー プロセスに送信し、シリアル化されたリストを取得できます (ただし、ここでの逆シリアル化にどれだけの費用がかかるかはまだ不明です)。固定サイズのブロックで入力を読み取るのは、予想される入力では難しいように思えますが、私の推測では、各行が必ずしも同じ長さであるとは限らないからです。

于 2013-08-12T02:09:28.283 に答える
1

ディクショナリの追加が遅い場合のより根本的な解決策: ディクショナリを文字列のペアの配列に置き換えます。埋めてから並べ替えます。

于 2013-08-16T20:28:57.630 に答える
-1

ファイル上のデータがそれほど頻繁に変更されない場合は、シリアル化することを選択できます。Python インタープリターは、はるかに迅速に逆シリアル化します。cPickle モジュールを使用できます。

または、8 つの個別のプロセスを作成することも別のオプションです。なぜなら、唯一の辞書を持つことで、それがはるかに可能になるからです。「マルチプロセッシング」モジュールまたは「ソケット」モジュールのパイプを介して、これらのプロセス間で対話できます。

よろしくお願いします

BarışÇUHADAR。

于 2013-11-09T21:07:08.960 に答える