メモリ管理をやってくれるPythonのガベージコレクションとは?

Pythonってどうやってメモリ管理してるんだろう
ガベージコレクションって何?

こんにちはフクロウです。Pythonのインストラクターをやっています。Pythonのメモリ管理について興味をもっている方、多いんじゃないでしょうか。

この記事ではPythonのメモリ管理の方法、特に「参照カウント」について紹介します。

「ガベージコレクションってやつが勝手に上手くやってくれてるんでしょ?」という認識の方には是非、この記事を読んでもらいたいです。うやむやにしていたところを理解して、Pythonをもっと楽しみましょう!

この記事はこんな人のために書きました。

  • Pythonのメモリ管理方法が知りたい
  • ガベージコレクションについて知りたい

ガベージコレクションについてわかりやすく解説していますので、ぜひ参考にしてください。

なお、Pythonの記事については、こちらにまとめています。

目次

ガベージコレクションってなんだろう

ガベージコレクション(Garbage collection)とは、必要なくなったメモリ領域を自動的に開放する機能です。

Pythonのプログラムは実行のために必要なメモリを自動的に確保してくれますが、GCはその後片付けをしてくれる機能だと思ってください。

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

Pythonには大きく分けて2つのガベージコレクションのコンポーネントがあります。これが参照カウントと世代別ガベージコレクションです。

参照カウント

これは非常にシンプルな考え方で動くガベージコレクションアルゴリズムです。プログラム中でオブジェクトへの参照がない場合に、そのオブジェクトの割当を解除(メモリを開放)します。

ここでいう参照カウントとは「そのオブジェクトが参照されている数」を記録した数字です。これはオブジェクトごとに用意されています。

参照カウントが増えるのは以下の処理を行ったときです。

  • 代入演算子を使ったとき
  • 引数渡しをしたとき
  • オブジェクトをコンテナ型オブジェクトに追加したとき

参照カウントが1以上のとき、メモリは確保されたままになります。逆に0になったとき、GCはそれを不要だと判断します。

参照カウントについてはsysモジュールで実際に確認することができるので、例を見てみましょう。

mylist = []

def myfunc(x):
    print("③ refcount of `mylist`: ",sys.getrefcount(mylist))

print("① refcount of `mylist`: ",sys.getrefcount(mylist))
print("② refcount of `myfunc`:",sys.getrefcount(myfunc))

myfunc(mylist)
print("④ refcount of `mylist`: ",sys.getrefcount(mylist))

[実行結果の例]

① refcount of `mylist`:  2
② refcount of `myfunc`:  2
③ refcount of `mylist`:  4
④ refcount of `mylist`:  2

実行結果を見ていきましょう。

①はmylistという変数が参照しているリスト型オブジェクトの参照カウントを表示しています。ここで、mylist変数とsys.getrefcount関数がそれぞれこのオブジェクトを参照しているので、カウントは2になります。

②はmyfuncという名前で定義された関数オブジェクトの参照カウントを表示しています。これも①と同様の理由でカウントは2です。関数であっても同様に扱われます。

③はmyfunc内での引数xが参照するオブジェクトの参照カウントです。ここでは、mylist、関数の引数、sys.getrefcount関数、そしてPythonの関数スタックが参照しているので4になります。

④はmyfuncの関数スコープから出た後なので、②と同様になります。

このカウントが0になるとGCがメモリを開放します。

世代別ガベージコレクション

参照カウント方式はシンプルで分かりやすいアルゴリズムですが、よく知られた落とし穴があります。

循環参照という状態になったとき、参照カウントが常に1以上になる(のでGCが動かない)というものです。

[循環参照の例]

mylist = []
mylist.append(mylist)

これはリスト型を始めとしたコンテナ型オブジェクトで起こる問題で、自分で自分自身を参照したり、2つのコンテナで相互に参照し合ったりすると発生します。この場合常に参照カウントは1以上なので、参照カウント方式のGCだけではメモリ解放が出来ません。

世代別ガベージコレクションではこのような場合にもGCを動かすための実装がされています。この方式は少しむずかしいので、ここでは解説しません。詳しく知りたい方は以下の記事を参考にしてください。

https://riptutorial.com/ja/python/topic/2532/garbage-collection

Pythonのガベージコレクションモジュールgc

Pythonには、ガベージコレクションを手動で動かすことができるモジュール「gc」があります。

これについては侍でも記事を公開しています:

何も考えずにとりあえずGCを動かすには以下の通りにやればOKです。

import gc
 
gc.collect() # 6108 と表示されました

gc.collect()は回収可能なオブジェクトを削除します。ただし以下のことに注意してください。

Python では、コードで使用されていたメモリーが OS に返されるという保証はありません。ガーベッジ・コレクションで保証されるのは、オブジェクトで使用されていたメモリーが収集され、将来のいずれかの時点において別のオブジェクトで使用されるために解放されるということのみです。前のセクションで説明したアプリケーション・フレームワークのメモリー・モデル・オプションを変更することが、長時間実行されるアプリケーションには重要です。プロセスを強制終了すると、他のコンポーネントで使用するためにメモリーが確実に解放されます。

アプリケーションのメモリ使用量の最適化 — IBM  より

memory_profilerライブラリのようなメモリ使用量を表示するツールを使うと、gcを使ってもメモリ量が変化してないように見えることがあります。これはPythonがコード実行に利用したメモリをOSに返さずにもっている可能性があるためです。

また、基本的に、GCは自分で明示的に利用する必要はありません。

メモリの管理が自動化されている点がPythonの便利な機能の一つであり、どうしても「開放されるはずのメモリが開放されない」という場面に遭遇した場合を除き、自分でgcモジュールを利用する必要はないはずです。

参考文献

Artem Golubin
https://rushter.com/blog/python-garbage-collector/

まとめ

この記事ではPythonのメモリ管理方法についてまとめました。Pythonではここで紹介した以外にも様々なメモリ管理のシステムが用意されています。

C言語のように明示的にメモリの管理をしないのがPythonの特徴なので、これに乗っかって手軽にプログラミングを楽しむのが王道です。ですがガベージコレクションの基本的な動作についてはおさえておいたほうがいいでしょう。

スクリプト言語でありながら様々な重い処理で利用されるPythonですから、メモリの効率的な扱い方を学ぶことは非常に重要です。この記事を皮切りに勉強をしていただければ嬉しいです。

この記事を書いた人

第一言語はPythonです。
皆さんRustやりましょう。

目次