Pythonのメモリ管理やガベージコレクションの仕組みを分かりやすく解説する

スポンサーリンク

メモリ管理とは、メモリを動的に保存し、使用しないときは解放する処理のことです。

スポンサーリンク

Pythonのメモリ管理を理解する

Pythonでは、このメモリの確保と解放は、python開発者が作成したPythonガベージコレクタによって自動的に行われるので、ユーザーは手動でガベージコレクションをする必要がありません。

ガベージコレクション

Pythonのガーベジコレクションは、インタープリタがプログラムのために未使用の不要なメモリを解放するメモリ管理プロセスです。

Pythonでは、これは自動的に行われます。

ガベージコレクタは、そのオブジェクトを指している参照がないオブジェクトを見つけ、ヒープメモリからそのオブジェクトを削除します。

このために、Pythonは参照カウントアルゴリズムを使用します。

例えば、以下の様になります。

class Python:
 
    def __init__(self):
        print('The object is created.')
 
    def __del__(self):
        print('The object is destroyed.')
 
obj1 = Python()
obj2 = obj1
obj3 = obj1
print("Set obj1 to None")
obj1 = None
print("Set obj2 to None")
obj2 = None
print("Set obj3 to None")
obj3 = None

結果は以下の通りです。

The object is created.
Set obj1 to None
Set obj2 to None
Set obj3 to None
The object is destroyed.

ここでは、Pythonクラスのオブジェクトを作成し、その参照をobj1, obj2, obj3に渡しました。

これにより、そのオブジェクトの参照カウントは3になっています。

そして、これらの参照をnoneに代入すると、そのオブジェクトからの参照はすべて削除され、0になります。

メソッドが実行されます。

参照カウント

Pythonの参照カウントは、オブジェクトを指している参照がないときにメモリからオブジェクトを解放する技術です。

参照カウントがゼロになると、そのオブジェクトは削除されます。

Python モジュール sys にある組み込み関数 getrefcount() は、与えられた Python オブジェクトの参照回数を返します。

例えば、以下の様になります。

import sys
str = "Welcome to Python"
print(sys.getrefcount(str))
 
arr = []
arr.append(str) # appending to an array
print(sys.getrefcount(str))
 
dict = {}
dict['str'] = str # adding to a dictionary
print(sys.getrefcount(str))
 
arr = [] # resetting the array
sys.getrefcount(str)
dict['my_str'] = "Some other string"
print(sys.getrefcount(str))

出力。

4
5
6
5

参照カウントの値は、関数sys.getrefcount()で渡されたオブジェクトの参照カウントもカウントしているため、期待値より1つ高い値になっています。

あるオブジェクトの参照カウントが0にならないことがあります。

これは、オブジェクトが自分自身を参照しているために起こります。

これを参照サイクルと呼びます。

例えば、以下の様になります。

import sys
x = []
x.append(x) # x contains reference to itself
print("Reference count of object is",sys.getrefcount(x))

出力。

Reference count of object is 3

ここで、自分自身を参照するオブジェクトxが作られる。

自分自身の参照を持つので、参照カウントが0になることはない。

オブジェクトxはPythonのガベージコレクタが起動されるまでメモリを占有します。

オブジェクトがグローバルに宣言されている場合、オブジェクトの参照カウントは決して0になることはありません。

メモリ割り当て

メモリの割り当てを理解するためには、ランダムアクセスメモリ(RAM)を理解する必要があります。

RAM は、コンピュータで情報を保存したり取り出したりするための主記憶装置とも呼ばれます。

RAM の最上部にはスタックがあり、最下部にはヒープがあります。

ヒープは変数/値を格納する役割を果たし、スタックはヒープ内のオブジェクトへの参照を保持する役割を果たします。

x = 5
y = x
if(id(x) == id(y)):
   print("x and y refer to the same object")
 
x = x+1
if(id(x) != id(y)):
    print("x and y refer to different objects")
 
z = 5
if(id(y) == id(y)):
   print("y and z refer to same memory")

Pythonでは、複数の変数が同じ値を持つとき、元の値を指す2番目の変数がヒープに作成されます。

例えば、以下の様になります。

x and y refer to the same object
x and y refer to different objects
y and z refer to same memory

出力。

def func():
    #These initializations are stored in stack memory
    x = 10
    y = "Apple"
     

メモリ割り当てには2種類あります。

  • スタックメモリの割り当て
  • ヒープメモリの割り当て

1. スタックメモリアロケーション

スタックメモリアロケーションとは、特定の関数やメソッド呼び出しの内部で静的メモリを格納することです。

関数が呼び出されると、メモリは関数呼び出しスタックに格納されます。

ローカル変数の初期化はすべてコールスタックに格納され、関数が戻ると削除されます。

したがって、プログラムを実行すると、すべての関数は最初にコールスタックに格納され、関数が返されると削除されます。

例えば、以下の様になります。

def func()
    #Allocates memory for 5 integers in heap memory
    x=[None]*5

2. ヒープメモリアロケーション

ヒープメモリの割り当てとは、特定の関数やメソッド呼び出しの外部で必要とされるメモリを保存することです。

このメモリは、プログラム内でグローバルスコープで使用されます。

ヒープメモリは、ヒープデータ構造とは関係ありません。

それは単に、ユーザーが変数/値を割り当てたり解放したりしたいときに提供されるメモリの大きな空間です。

Pythonでは、ヒープメモリはインタプリタ自身が管理し、ユーザはそれを制御することができません。

例えば、以下の様になります。

Stack
Stack and Heap locations in RAM

まとめ

プログラマがPythonに惚れ込んだのは、その優れたメモリ管理能力のためでした。

他の多くの低レベルプログラミング言語と比較して、Pythonはリソースの使いすぎを心配することなく、変数を簡単に扱えるようにしました。

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