メモリ使用量が着実に増加していることから判断すると、私の Python アプリケーションの 1 つがメモリ リークしているように見えます。これを回避するための最善の努力にもかかわらず、私の仮説はどこかで循環参照です。問題を特定するために、私は到達不能アイテムを手動でチェックする方法を検討しています。これは、純粋にデバッグを目的としたツールです。
gc モジュールは必要な追跡ができるようで、最後の呼び出し以降に形成された到達不能アイテムのリストをコンパイルすることを目的とした次のコードを試みました。最初の呼び出しは基本チェックポイントを設定するだけで、到達不能アイテムは識別されません。
def unreachable():
# first time setup
import gc
gc.set_threshold( 0 ) # only manual sweeps
gc.set_debug( gc.DEBUG_SAVEALL ) # keep unreachable items as garbage
gc.enable() # start gc if not yet running (is this necessary?)
# operation
if gc.collect() == 0:
return 'no unreachable items'
s = 'unreachable items:\n ' \
+ '\n '.join( '[%d] %s' % item for item in enumerate( gc.garbage ) )
_deep_purge_list( gc.garbage ) # remove unreachable items
return s # return unreachable items as text
ここで、_deep_purge_list は循環を断ち切り、オブジェクトを手動で削除することを目的としています。次の実装は、いくつかの一般的なケースを処理しますが、完全に密閉されているわけではありません。私の最初の質問はこれに関連しています。下を参照してください。
def _deep_purge_list( garbage ):
for item in garbage:
if isinstance( item, dict ):
item.clear()
if isinstance( item, list ):
del item[:]
try:
item.__dict__.clear()
except:
pass
del garbage[:]
非常に限定的なテストに基づくと、セットアップは適切に機能しているように見えます。次の循環参照は、一度だけ正しく報告します。
class A( object ):
def __init__( self ):
self.ref = self
print unreachable()
# no unreachable items
A()
print unreachable()
# unreachable items:
# [0] <__main__.A object at 0xb74579ac>
# [1] {'ref': <__main__.A object at 0xb74579ac>}
print unreachable()
# no unreachable items
ただし、次の場合、奇妙なことが起こります。
print unreachable()
# no unreachable items
import numpy
print unreachable()
# unreachable items:
# [0] (<type '_ctypes.Array'>,)
# [1] {'__module__': 'numpy.ctypeslib', '__dict__': <attribute '__dict__' of 'c_long_Array_1' objects>, '__weakref__': <attribute '__weakref__' of 'c_long_Array_1' objects>, '_length_': 1, '_type_': <class 'ctypes.c_long'>, '__doc__': None}
# [2] <class 'numpy.ctypeslib.c_long_Array_1'>
# [3] <attribute '__dict__' of 'c_long_Array_1' objects>
# [4] <attribute '__weakref__' of 'c_long_Array_1' objects>
# [5] (<class 'numpy.ctypeslib.c_long_Array_1'>, <type '_ctypes.Array'>, <type '_ctypes._CData'>, <type 'object'>)
print unreachable()
# unreachable items:
# [0] (<type '_ctypes.Array'>,)
# [1] {}
# [2] <class 'c_long_Array_1'>
# [3] (<class 'c_long_Array_1'>, <type '_ctypes.Array'>, <type '_ctypes._CData'>, <type 'object'>)
呼び出しを繰り返すと、最後の結果が返され続けます。インポート後に unreachable が初めて呼び出された場合、問題は発生しません。ただし、現時点では、この問題が numpy 固有のものであると信じる理由はありません。私の推測では、私のアプローチの欠陥が露呈していると思います。
私の質問:
- gc.garbage のアイテムを削除するより良い方法はありますか? 理想的には、DEBUG_SAVEALL なしで行ったように (すべきでしょうか?)、gc にそれらを削除させる方法はありますか?
- 誰もnumpy importの問題を説明したり、修正する方法を提案したりできますか?
結果論:
以下のコードは、意図したとおりに動作するようです。
def unreachable():
import gc
gc.set_threshold( 0 )
gc.set_debug( gc.DEBUG_LEAK )
gc.enable()
print 'collecting {{{'
gc.collect()
print '}}} done'
ただし、デバッグの場合は、gc が提供する type/id よりもリッチな文字列表現を好みます。さらに、以前のアプローチの欠陥を理解し、gc モジュールについて何かを学びたいと思います。
あなたの助けに感謝します,
ゲルジャン
更新 06/05:
locals() がその直前に呼び出されない限り (戻り値を破棄する)、最初の実装では到達不能アイテムが報告されないという状況に遭遇しました。これがgcのオブジェクト追跡にどのように影響する可能性があるかを理解していないため、さらに混乱しています。この問題を示す小さな例を作成するのがどれほど簡単かはわかりませんが、必要に応じて試してみることができます。