web-dev-qa-db-ja.com

Python Jupyter Notebookでガベージコレクションが機能しないことがある

私は常にRAMをいくつかのJupyterノートブックで使い果たしており、不要になったメモリを解放することができないようです。ここに例があります:

import gc
thing = Thing()
result = thing.do_something(...)
thing = None
gc.collect()

ご想像のとおり、thingは何かをするために大量のメモリを使用しますが、その後はもう必要ありません。使用しているメモリを解放できるはずです。ノートブックからアクセスできる変数には書き込みませんが、ガベージコレクターはスペースを適切に解放していません。私が見つけた唯一の回避策は、resultをpickleに書き込んでカーネルを再起動し、resultをpickleからロードして続行することです。これは、長いノートブックを実行するときは本当に不便です。どうすればメモリを適切に解放できますか?

12
Atte Juvonen

ここには多くの問題があります。 1つ目は、IPython(Jupyterが舞台裏で使用するもので、_Out[67]_のようなものが表示されたときにオブジェクトへの追加の参照が保持されることです。実際、その構文を使用してオブジェクトを呼び出し、何かを実行できます。例:str(Out[67]) 2つ目の問題は、Jupyterが出力変数の独自の参照を保持しているように見えるため、IPythonの完全なリセットのみが機能することですが、ノートブックを再起動するだけの場合とそれほど変わりません。

しかし解決策があります!明示的に保持するように要求したものを除いて、すべての変数をクリアする、実行できる関数を作成しました。

_def my_reset(*varnames):
    """
    varnames are what you want to keep
    """
    globals_ = globals()
    to_save = {v: globals_[v] for v in varnames}
    to_save['my_reset'] = my_reset  # lets keep this function by default
    del globals_
    get_ipython().magic("reset")
    globals().update(to_save)
_

あなたはそれを次のように使うでしょう:

_x = 1
y = 2
my_reset('x')
assert 'y' not in globals()
assert x == 1
_

以下に、裏で何が起こっているのかを少しだけ示し、weakrefモジュールを使用して何かが本当に削除されたときを確認する方法を示すノートブックを書きました。それを実行して、何が起こっているのかを理解するのに役立つかどうかを確認できます。

_In [1]: class MyObject:
            pass

In [2]: obj = MyObject()

In [3]: # now lets try deleting the object
        # First, create a weak reference to obj, so we can know when it is truly deleted.
        from weakref import ref
        from sys import getrefcount
        r = ref(obj)
        print("the weak reference looks like", r)
        print("it has a reference count of", getrefcount(r()))
        # this prints a ref count of 2 (1 for obj and 1 because getrefcount
        # had a reference to obj)
        del obj
        # since obj was the only strong reference to the object, it should have been 
        # garbage collected now.
        print("the weak reference looks like", r)

the weak reference looks like <weakref at 0x7f29a809d638; to 'MyObject' at 0x7f29a810cf60>
it has a reference count of 2
the weak reference looks like <weakref at 0x7f29a809d638; dead>

In [4]: # lets try again, but this time we won't print obj, will just do "obj"
        obj = MyObject()

In [5]: print(getrefcount(obj))
        obj

2
Out[5]: <__main__.MyObject at 0x7f29a80a0c18>

In [6]: # note the "Out[5]". This is a second reference to our object
        # and will keep it alive if we delete obj
        r = ref(obj)
        del obj
        print("the weak reference looks like", r)
        print("with a reference count of:", getrefcount(r()))

the weak reference looks like <weakref at 0x7f29a809db88; to 'MyObject' at 0x7f29a80a0c18>
with a reference count of: 7

In [7]: # So what happened? It's that Out[5] that is keeping the object alive.
        # if we clear our Out variables it should go away...
        # As it turns out Juypter keeps a number of its own variables lying around, 
        # so we have to reset pretty everything.

In [8]: def my_reset(*varnames):
            """
            varnames are what you want to keep
            """
            globals_ = globals()
            to_save = {v: globals_[v] for v in varnames}
            to_save['my_reset'] = my_reset  # lets keep this function by default
            del globals_
            get_ipython().magic("reset")
            globals().update(to_save)

        my_reset('r') # clear everything except our weak reference to the object
        # you would use this to keep "thing" around.

Once deleted, variables cannot be recovered. Proceed (y/[n])? y

In [9]: print("the weak reference looks like", r)

the weak reference looks like <weakref at 0x7f29a809db88; dead>
_
9
Dunes