web-dev-qa-db-ja.com

Python-メモリリークの回避

一連の実験を実行するPythonプログラムがあり、あるテストから別のテストに保存することを目的としたデータはありません。私のコードには、完全に見つけることができないメモリリークが含まれています(I ' 他のスレッド メモリリークについて見てください。時間の制約のため、リークの発見をあきらめなければなりませんでしたが、各実験を分離できた場合、プログラムはおそらく長く実行されます。必要な結果を生み出すのに十分です。

  • 各テストを別々のスレッドで実行すると役立ちますか?
  • リークの影響を分離する他の方法はありますか?

特定の状況の詳細

  • 私のコードには、実験ランナーと実際の実験コードの2つの部分があります。
  • すべての実験を実行するためのコードと各実験で使用されるコードの間でグローバルは共有されませんが、一部のクラス/関数は必然的に共有されます。
  • 実験ランナーは、シェルスクリプトに簡単に挿入できる単純なforループではありません。最初に、構成パラメーターを指定して実行する必要のあるテストを決定し、次にテストを実行してから、特定の方法でデータを出力します。
  • ガベージコレクションが実行されていないことが問題である場合に備えて、ガベージコレクターを手動で呼び出してみましたが、これは機能しませんでした

更新

Gnibblerの回答により、実際には、各計算中に使用されるデータのallを格納するClosenessCalculationオブジェクトが強制終了されていないことがわかりました。次に、それを使用して、メモリの問題を修正したと思われるいくつかのリンクを手動で削除しました。

30
Casebash

このようなものを使用して、メモリリークを追跡できます

>>> from collections import defaultdict
>>> from gc import get_objects
>>> before = defaultdict(int)
>>> after = defaultdict(int)
>>> for i in get_objects():
...     before[type(i)] += 1 
... 

ここで、テストでメモリがリークするとします。

>>> leaked_things = [[x] for x in range(10)]
>>> for i in get_objects():
...     after[type(i)] += 1
... 
>>> print [(k, after[k] - before[k]) for k in after if after[k] - before[k]]
[(<type 'list'>, 11)]

11さらに10個のリストを含む1つのリストをリークしたため

57
John La Rooy

スレッドは役に立ちません。リークの発見をあきらめなければならない場合、その影響を抑える唯一の解決策は、新しいprocessを時々実行することです(たとえば、テストで全体的なメモリ消費量が多すぎて好みに合わない場合など) --Linuxで/proc/self/statusを読み取ることで、VMサイズ、および他のOSでの他の同様のアプローチ)を簡単に決定できます。

スクリプト全体がオプションのパラメーターを取り、どのテスト番号(または他のテストID)から開始するかを指定するようにしてください。これにより、スクリプトの1つのインスタンスが、メモリを大量に消費していると判断した場合に、後続のインスタンスにどこから再開するかを指示できます。 。

または、より確実に、各テストが完了すると、そのIDが既知の名前のファイルに追加されることを確認してください。プログラムが起動すると、そのファイルを読み取ることから始まり、どのテストがすでに実行されているかがわかります。このアーキテクチャは、テスト中にプログラムクラッシュの場合もカバーするため、より堅牢です。もちろん、このようなクラッシュからの回復を完全に自動化するには、別のウォッチドッグプログラムとプロセスが、前のインスタンスがクラッシュしたと判断したときにテストプログラムの新しいインスタンスの開始を担当する必要があります(subprocess目的のために-シーケンスがいつ終了したかを知る方法も必要です。たとえば、テストプログラムを通常どおり終了すると、クラッシュまたはステータスが!= 0の終了は、新しいフレッシュを開始する必要があることを意味します。インスタンス)。

これらのアーキテクチャが魅力的であるが、それらの実装についてさらに支援が必要な場合は、この回答にコメントしてください。サンプルコードを提供させていただきます。まだ表現されていない問題がある場合に備えて、「先制的に」実行したくありません。そのため、アーキテクチャが不適切になります。 (実行する必要のあるプラットフォームを知ることも役立つ場合があります)。

4
Alex Martelli

リークしていたサードパーティのCライブラリでも同じ問題が発生しました。私が考えることができた最もクリーンな回避策は、フォークして待つことでした。その利点は、実行するたびに個別のプロセスを作成する必要がないことです。バッチのサイズを定義できます。

一般的な解決策は次のとおりです(リークが見つかった場合、実行する必要がある唯一の変更は、run_forked()ではなくrun_single_process()を呼び出すようにrun()を変更することです。これで完了です):

import os,sys
batchSize = 20

class Runner(object):
    def __init__(self,dataFeedGenerator,dataProcessor):
        self._dataFeed = dataFeedGenerator
        self._caller = dataProcessor

    def run(self):
        self.run_forked()

    def run_forked(self):
        dataFeed = self._dataFeed
        dataSubFeed = []
        for i,dataMorsel in enumerate(dataFeed,1):
            if i % batchSize > 0:
                dataSubFeed.append(dataMorsel)
            else:
                self._dataFeed = dataSubFeed
                self.fork()
                dataSubFeed = []
                if self._child_pid is 0:
                    self.run_single_process()
                self.endBatch()

    def run_single_process(self)
        for dataMorsel in self._dataFeed:
            self._caller(dataMorsel)

    def fork(self):
        self._child_pid = os.fork()

    def endBatch(self):
        if self._child_pid is not 0:
            os.waitpid(self._child_pid, 0)
        else:
            sys.exit() # exit from the child when done

これにより、子プロセスへのメモリリークが分離されます。また、batchSize変数の値よりも多くの回数リークすることはありません。

3

実験を個々の関数にリファクタリングし(まだそうでない場合)、単一の実験関数を呼び出すコマンドラインから実験番号を受け入れます。

シェルスクリプトを次のようにまとめるだけです。

#!/bin/bash

for expnum in 1 2 3 4 5 6 7 8 9 10 11 ; do
    python youProgram ${expnum} otherParams
done

そうすれば、ほとんどのコードをそのままにしておくことができます。これにより、各実験の間にあると思われるメモリリークが解消されます。

もちろん、最善の解決策は常に問題の根本原因を見つけて修正することですが、すでに述べたように、それはあなたにとって選択肢ではありません。

Pythonでのメモリリークを想像するのは難しいですが、私はあなたのWordをその1つに取ります-しかし、少なくともあなたがそこで間違っている可能性を考慮したいかもしれません。別の質問でthatを上げることを検討してください。これは、(このクイックフィックスバージョンとは対照的に)低い優先度で取り組むことができるものです。

更新:質問が元の質問から多少変更されたため、コミュニティwikiを作成しています。答えは削除しますが、それでも役立つと思います。bashスクリプトを提案したのと同じことを実験ランナーに行うことができます。メモリリークが発生しないように、実験が別々のプロセスであることを確認する必要があります。 (メモリリークがランナーにある場合は、根本原因の分析を行い、バグを適切に修正する必要があります)。

2
paxdiablo