web-dev-qa-db-ja.com

Python readlines()の使用法と効率的な読み取り方法

フォルダー内の数千のテキストファイル(〜400 KBサイズの各ファイルで約3000行)を解析するのに問題があります。私はreadlinesを使用してそれらを読みました、

   for filename in os.listdir (input_dir) :
       if filename.endswith(".gz"):
          f = gzip.open(file, 'rb')
       else:
          f = open(file, 'rb')

       file_content = f.readlines()
       f.close()
   len_file = len(file_content)
   while i < len_file:
       line = file_content[i].split(delimiter) 
       ... my logic ...  
       i += 1  

これは、入力(50,100ファイル)からのサンプルでは完全に正常に機能します。 5Kファイルを超える入力全体を実行した場合、かかる時間は線形増分に近いものではありませんでした。パフォーマンス分析を行うことを計画し、Cprofile分析を行いました。入力が7Kファイルに達すると、より多くのファイルが指数関数的に増加し、悪いレートに達するのにかかる時間。

Readlines、最初の-> 354個のファイル(入力からのサンプル)、および2番目の-> 7473個のファイル(入力全体)にかかった累積時間を次に示します。

 ncalls  tottime  percall  cumtime  percall filename:lineno(function)
 354    0.192    0.001    **0.192**    0.001 {method 'readlines' of 'file' objects}
 7473 1329.380    0.178  **1329.380**    0.178 {method 'readlines' of 'file' objects}

このため、入力が増加しても、コードにかかる時間は直線的にスケーリングしません。私はreadlines()に関するドキュメントノートを読みます。人々は、このreadlines()はファイルのコンテンツ全体をメモリに読み込むため、一般的にreadline()read()

私はこの点に同意しますが、ガベージコレクターは、ループの最後にメモリからロードされたコンテンツを自動的にクリアする必要があります。したがって、メモリは現在処理中のファイルのコンテンツのみを持つべきです。しかし、ここにはいくつかの落とし穴があります。誰かがこの問題に関する洞察を与えることができます。

これはreadlines()の固有の動作ですか、またはpythonガベージコレクターの間違った解釈です。

また、メモリと時間の効率的な方法で同じことを行ういくつかの代替方法を提案します。 TIA。

37
Learner

短いバージョンは次のとおりです。 readlines()を使用する効率的な方法は、使用しないことです。Ever。


私はreadlines()に関するドキュメントノートを読みます。このreadlines()はファイルの内容全体をメモリに読み込むため、一般にreadline()またはread()と比較してより多くのメモリを消費すると主張しています。

readlines()のドキュメントは、ファイル全体をメモリに読み込み、解析することを明示的に保証します行、およびこれらの行からlistingsでいっぱいのstrを構築します。

しかし、 read() のドキュメントは同様に、ファイル全体をメモリに読み込み、stringを構築することを保証しているため、役に立ちません。


これは、より多くのメモリを使用することに加えて、すべてが読み取られるまで作業を実行できないことも意味します。最も単純な方法で読み取りと処理を交互に行う場合、少なくともいくつかのパイプライン処理(OSディスクキャッシュ、DMA、CPUパイプラインなどのおかげ)の恩恵を受けるため、次のバッチ中に1つのバッチで作業することになります。読んでいます。ただし、コンピューターにファイル全体を読み取らせ、ファイル全体を解析してからコードを実行するように強制すると、読み取りごとに重複する作業の1つの領域ではなく、ファイル全体に対して重複する作業の1つの領域のみが得られます。


これを回避するには、次の3つの方法があります。

  1. readlines(sizehint)read(size)、またはreadline()の周りにループを作成します。
  2. これらを呼び出さずに、ファイルを遅延イテレータとして使用するだけです。
  3. mmapファイル。最初に読み込むことなく、巨大な文字列として扱うことができます。

たとえば、これはすべてのfooを一度に読み取る必要があります。

with open('foo') as f:
    lines = f.readlines()
    for line in lines:
        pass

しかし、これは一度に約8Kしか読み取れません。

with open('foo') as f:
    while True:
        lines = f.readlines(8192)
        if not lines:
            break
        for line in lines:
            pass

そして、これは一度に1行だけを読み取ります。ただし、Pythonは、処理を高速化するためにNiceバッファーサイズを選択できます(および選択します)。

with open('foo') as f:
    while True:
        line = f.readline()
        if not line:
            break
        pass

そして、これは前とまったく同じことを行います:

with open('foo') as f:
    for line in f:
        pass

その間:

しかし、ガベージコレクターは、ループの終わりにロードされたコンテンツをメモリから自動的にクリアする必要がありますか?したがって、メモリは現在処理されているファイルのコンテンツのみを持つ必要がありますか?

Pythonは、ガベージコレクションについてこのような保証を行いません。

CPythonの実装はたまたまGCの参照カウントを使用しています。つまり、コード内でfile_contentがリバウンドまたは消滅し、文字列の巨大なリストとその中のすべての文字列がフリーリストに解放されます。つまり、同じメモリを次のパスで再利用できます。

ただし、これらの割り当て、コピー、および割り当て解除はすべて無料ではありません。実行するよりも実行しないほうがはるかに高速です。

その上、同じ小さなメモリチャンクを何度も繰り返し使用するのではなく、文字列を大量のメモリに分散させると、キャッシュの動作が損なわれます。

さらに、メモリ使用量は一定(または、ファイルサイズの合計ではなく、最大ファイルのサイズで線形)である場合がありますが、mallocsのラッシュは初めてそれを拡張します最も遅い処理の1つになります(これにより、パフォーマンスの比較が非常に難しくなります)。


すべてをまとめると、次のようにプログラムを記述します。

for filename in os.listdir(input_dir):
    with open(filename, 'rb') as f:
        if filename.endswith(".gz"):
            f = gzip.open(fileobj=f)
        words = (line.split(delimiter) for line in f)
        ... my logic ...  

または多分:

for filename in os.listdir(input_dir):
    if filename.endswith(".gz"):
        f = gzip.open(filename, 'rb')
    else:
        f = open(filename, 'rb')
    with contextlib.closing(f):
        words = (line.split(delimiter) for line in f)
        ... my logic ...
74
abarnert

ファイル全体ではなく、1行ずつ読み取ります。

for line in open(file_name, 'rb'):
    # process line here

withを使用して、ファイルを自動的に閉じることもできます。

with open(file_name, 'rb') as f:
    for line in f:
        # process line here

上記は、イテレータを使用して、一度に1行ずつファイルオブジェクトを読み取ります。

16
Óscar López