Pythonのメモリ管理の基本

メモリ管理とは、すべての異なるプロセスがスムーズに動作し、異なるシステム資源に最適にアクセスできるように、効率的にメモリを割り当て、割り当て解除し、調整するプロセスです。また、メモリ管理には、アクセスされなくなったオブジェクトのメモリを掃除することも含まれます。

Pythonでは、メモリマネージャが定期的に実行されて、メモリのクリーンアップ、割り当て、管理を行うことで、この種のタスクを担当します。CやJavaなどのプログラミング言語とは異なり、Pythonは参照カウントを使用してオブジェクトを管理します。つまり、メモリマネージャはプログラム内の各オブジェクトへの参照回数を記録しています。あるオブジェクトの参照カウントがゼロになると、つまりそのオブジェクトがもう使われていないことを示すと、ガベージコレクタ(メモリマネージャの一部)が自動的にその特定のオブジェクトからメモリを解放するのです。

メモリの割り当てと解放のプロセスは完全に自動化されているため、ユーザーはメモリ管理について心配する必要はない。回収されたメモリは、他のオブジェクトが使用することができる。

Python ガーベッジコレクション

先ほど説明したように、Pythonはプログラム内で参照されなくなったオブジェクトを削除して、メモリ領域を解放しています。このように、Pythonが使われなくなったメモリブロックを解放する処理をガーベジコレクションと呼びます。Pythonのガーベジコレクタ(GC)はプログラムの実行中に実行され、参照カウントがゼロになったときにトリガされます。参照カウントは、オブジェクトに新しい名前が割り当てられたり、タプルや辞書のようなコンテナに配置されたりすると増加します。同様に、オブジェクトへの参照が再割り当てされたり、オブジェクトの参照がスコープ外になったり、オブジェクトが削除されたりすると、参照カウントは減少する。

メモリはヒープであり、プログラム内で使用されるオブジェクトやその他のデータ構造が格納されている。このヒープ空間の割り当てと解放は、API関数の使用によりPythonメモリマネージャによって制御されます。

メモリ上のPythonオブジェクト

Pythonの各変数は、オブジェクトとして動作します。オブジェクトには、単純なもの(数値や文字列など)と、コンテナ(辞書、リスト、ユーザー定義クラス)があります。さらに、Pythonは動的型付け言語であり、プログラムで使用する前に変数やその型を宣言する必要がないことを意味します。

例えば

>>> x = 5
>>> print(x)
5
>>> del x
>>> print(x)
Traceback (most reent call last):
  File "<mem_manage", line 1, in <module
    print(x)
NameError : name 'x' is not defined


上のプログラムの最初の2行を見ると、オブジェクト x は既知である。このオブジェクト x を削除して使おうとすると、変数 x が定義されていないというエラーになります。

Pythonのガベージコレクションは完全に自動化されており、C言語のようにプログラマが心配する必要がないことがわかります。

ガベージコレクタの改造

Python のガベージコレクタには、オブジェクトが分類される3つの世代があります。新しいオブジェクトのライフサイクルが始まった時点では、ガベージコレクタの第一世代になります。オブジェクトがガベージコレクションに耐えられるようになると、次の世代に移行します。ガベージコレクタの3世代には、それぞれ閾値がある。具体的には、アロケーションの数からデアロケーションの数を引いた閾値を超えると、その世代はガベージコレクションを実行する。

また、早い世代は高い世代よりも頻繁にガベージコレクションが行われる。これは、新しいオブジェクトの方が古いオブジェクトよりも捨てられる可能性が高いからである。

gcモジュールには、閾値を変更したり、手動でガベージコレクション処理を起動したり、ガベージコレクション処理を無効にしたりするための関数が含まれている。get_threshold() メソッドを使うと、異なる世代のガベージコレクタの閾値を確認することができます。

import gc
print(gc.get_threshold())


サンプル出力です。

(700, 10, 10)


ご覧の通り、ここでは第一世代の閾値が700、他の2世代はそれぞれ10となっています。

ガベージコレクション処理を起動するための閾値は、gc モジュールの set_threshold() メソッドを用いて変更することができます。

gc.set_threshold(900, 15, 15)


上の例では、3世代すべてについてしきい値を大きくしています。しきい値を大きくすると、ガベージコレクタを実行する頻度が減ります。通常、開発者としてPythonのガベージコレクションについて深く考える必要はありませんが、ターゲットシステム向けにPythonランタイムを最適化する際に役立つ場合があります。主な利点の1つは、Pythonのガベージコレクション機構が開発者のために多くの低レベルの詳細を自動的に処理することです。

なぜ手動でガベージコレクションを行うのか?

Python インタープリタはプログラムで使用されるオブジェクトへの参照を追跡していることが分かっています。Pythonの以前のバージョン(バージョン1.6まで)では、Pythonインタープリタはメモリを扱うのに参照カウントの仕組みだけを使っていました。参照カウントが0になると、Pythonインタープリタは自動的にメモリを解放します。この古典的な参照カウントの仕組みは非常に効果的ですが、プログラムが参照サイクルを持つ場合に機能しないことを除けば、非常に効果的です。参照サイクルは、1つ以上のオブジェクトが互いに参照しあっている場合に発生し、そのため参照カウントは決してゼロになりません。

例を見てみよう。

&gt;&gt;&gt; def create_cycle():
...     list = [8, 9, 10]
...     list.append(list)
...     return list
... 
&gt;&gt;&gt; create_cycle()
[8, 9, 10, [...]]


上のコードでは、オブジェクト list が自分自身を参照する参照サイクルが発生します。したがって、オブジェクト list のメモリは、関数が戻ったときに自動的に解放されることはありません。参照サイクルの問題は、参照カウントでは解決できない。しかし、この参照サイクルの問題は、Python アプリケーションのガベージコレクタの動作を変更することで解決することができます。

そのためには、gcモジュールの gc.collect() 関数を使用します。

import gc
n = gc.collect()
print("Number of unreachable objects collected by GC:", n)


gc.collect()` は、収集し、割り当てを解除したオブジェクトの数を返します。

手動でガベージコレクションを行うには、タイムベース・ガベージコレクションとイベントベース・ガベージコレクションの2つの方法があります。

時間ベースのガベージコレクションは非常に単純で、一定の時間間隔をおいて gc.collect() 関数が呼ばれます。

イベントベースのガベージコレクションは、イベントが発生した後 (つまり、アプリケーションが終了したときや、アプリケーションが特定の時間アイドル状態だったとき) に gc.collect() 関数が呼び出されます。

いくつかの参照サイクルを作ることで、手動ガベージコレクションの作業を理解しましょう。

import sys, gc


def create_cycle():
    list = [8, 9, 10]
    list.append(list)


def main():
    print("Creating garbage...")
    for i in range(8):
        create_cycle()


print("Collecting...")
    n = gc.collect()
    print("Number of unreachable objects collected by GC:", n)
    print("Uncollectable garbage:", gc.garbage)


if __name__ == "__main__":
    main()
    sys.exit()


出力は以下のようになります。

Creating garbage...
Collecting...
Number of unreachable objects collected by GC: 8
Uncollectable garbage: []


上のスクリプトはリストオブジェクトを作成し、それを list という変数で参照します。リストオブジェクトの最初の要素は自分自身を参照しています。リストオブジェクトの参照カウントは、それがプログラム中で削除されたり、スコープ外になったりしても、常に 0 よりも大きいです。したがって、list オブジェクトは循環参照によりガベージコレクションされません。Python のガベージコレクタ機構は、定期的に循環参照を自動的にチェックし、回収します。

上記のコードでは、参照カウントが少なくとも1であり、決して0にはならないので、 gc.collect() を呼び出してオブジェクトを強制的にガベージコレクションしています。しかし、強制的にガベージコレクションを頻繁に行わないように注意してください。なぜなら、メモリを解放した後でも、GCはオブジェクトがガベージコレクションされる資格があるかどうかを評価するのに時間がかかり、プロセッサの時間とリソースを消費してしまうからです。また、アプリが完全に起動した後にのみ、ガベージコレクタを手動で管理することを忘れないでください。

結論

この記事では、Pythonのメモリ管理が参照カウントとガベージコレクション戦略を使用することによって自動的に処理される方法について説明しました。ガベージコレクションがなければ、Pythonでうまくメモリ管理の仕組みを実装することは不可能です。また、割り当てられたメモリの削除はPythonのメモリマネージャが行うので、プログラマは心配する必要がありません。これは、より少ないメモリリークとより良い性能につながります。

を参照してください。

タイトルとURLをコピーしました