ホームページ  >  記事  >  バックエンド開発  >  Python のガベージ コレクション メカニズムをマスターする方法。

Python のガベージ コレクション メカニズムをマスターする方法。

PHPz
PHPz転載
2023-05-08 22:10:24963ブラウズ

Python の自動ガベージ コレクション メカニズムのおかげで、Python でオブジェクトを作成するときにオブジェクトを手動で解放する必要はありません。これは開発者にとって非常に使いやすく、開発者は低レベルのメモリ管理について心配する必要がなくなります。ただし、ガベージ コレクションのメカニズムを理解していないと、作成する Python コードは非常に非効率なものになることがよくあります。

ガベージ コレクション アルゴリズムは多数あります。主なものは、参照カウントマーククリアランス世代コレクションなどです。

python では、ガベージ コレクション アルゴリズムは主に 参照カウント マーククリアランス 、および 世代別コレクション に基づいています。 2つの機構が補足されています。

1 参照カウント

1.1 参照カウント アルゴリズムの原理

参照カウントの原理は比較的単純です:

各オブジェクトには整数の参照カウント属性があります。 。オブジェクトが参照された回数を記録するために使用されます。たとえば、オブジェクト AA を参照する場合、A の参照カウントは 1 になります。参照が削除されると、#A の参照数は -1 になります。 A の参照カウントが 0 の場合、オブジェクト A は使用できなくなり、直接リサイクルされることを意味します。

Python では、sys モジュールの getrefcount 関数を通じて、指定されたオブジェクトの参照カウンターの値を取得できます。実際の例で見てみましょう。

import sys

class A():
    def __init__(self):
        pass
        
a = A()
print(sys.getrefcount(a))

上記のコードを実行すると、出力結果が

2 として得られます。

1.2 カウンタの増減条件

上記では、

A オブジェクトを作成し、そのオブジェクトを a 変数 (参照カウンタ) に代入した後を見てきました。オブジェクト 値は 2 です。それでは、カウンタが 1 になるのはいつでしょうか。また、カウンタが -1 になるのはいつでしょうか?

1.2.1 参照カウントの条件 1
A()
a=A()
func(a)
arr=[a,a]
1.2.2 参照カウント -1
オブジェクトが明示的に破棄される (例:

del a##) # 。変数は、a=0 などの新しいオブジェクトに再割り当てされます。オブジェクトは、func などのスコープを離れます。関数の実行が完了すると、関数内の func ローカル変数が取り出されます (グローバル変数は取り出されません)。 オブジェクトが配置されているコンテナが破棄されるか、オブジェクトがコンテナから削除されます。

1.2.3 コードの練習

カウンタの増加と減少をよりよく理解するために、実際のコードを実行して、一目でそれを明確に確認します。
import sys
 
class A():

    def __init__(self):
        pass
 
print("创建对象 0 + 1 =", sys.getrefcount(A()))

a = A()
print("创建对象并赋值 0 + 2 =", sys.getrefcount(a))

b = a
c = a
print("赋给2个变量 2 + 2 =", sys.getrefcount(a))

b = None
print("变量重新赋值 4 - 1 =", sys.getrefcount(a))

del c
print("del对象 3 - 1 =", sys.getrefcount(a))

d = [a, a, a]
print("3次加入列表 2 + 3 =", sys.getrefcount(a))


def func(c):
    print('传入函数 1 + 2 = ', sys.getrefcount(c))
func(A())

出力結果は次のとおりです。

创建对象 0 + 1 = 1
创建对象并赋值 0 + 2 = 2
赋给2个变量 2 + 2 = 4
变量重新赋值 4 - 1 = 3
del对象 3 - 1 = 2
3次加入列表 2 + 3 = 5
传入函数 1 + 2 =  3

1.3 参照カウントの利点と欠点

1.3.1 参照カウントの利点

    効率的、ロジックは単純で、ルールに従ってカウンターを加算および減算するだけです。 ############リアルタイム。オブジェクトのカウンタがゼロになると、そのオブジェクトは二度と使用できなくなり、メモリを直接解放するために特定の時間を待つ必要はなくなります。
  • 1.3.2 参照カウントの欠点
  • オブジェクトに参照カウント領域を割り当てる必要があるため、メモリ消費量が増加します。

  • 解放する必要があるオブジェクトが辞書オブジェクトなど比較的大きい場合、参照されているすべてのオブジェクトをループで呼び出してネストする必要があり、時間がかかることがあります。
循環参照。これは参照カウントの致命的な欠陥であり、参照カウントには解決策がないため、他のガベージ コレクション アルゴリズムを使用して補う必要があります。

2 Mark-Clear

前のセクションで説明したように、参照カウント アルゴリズムでは循環参照の問題を解決できません。循環参照されたオブジェクトは、カウンタが永遠に

0Python のガベージ コレクション メカニズムをマスターする方法。 に等しくないため、リサイクルできないという問題が発生します。

Mark-Clear

このアルゴリズムは主に潜在的な循環参照問題に使用され、次の 2 つのステップに分かれています:

マーキング段階。すべてのオブジェクトをグラフのノードとして扱い、オブジェクトの参照関係に基づいてグラフ構造を構築します。すべてのオブジェクトはグラフのルート ノードからトラバースされ、訪問されたすべてのオブジェクトは、オブジェクトが「到達可能」であることを示すためにマークされます。

    フェーズをクリアします。すべてのオブジェクトを走査し、「到達可能」とマークされていないオブジェクトが見つかった場合、そのオブジェクトはリサイクルされます。
  1. 具体的なコード例で説明します:
  2. class A():
        def __init__(self):
            self.obj = None
     
    def func():
        a = A()
        b = A()
        c = A()
        d = A()
    
        a.obj = b
        b.obj = a
        return [c, d]
    
    e = func()

    上記のコードでは、a と b は相互に参照し、e は c と d を参照します。参照関係全体を次の図に示します。

#参照カウンタ アルゴリズムが使用される場合、2 つのオブジェクト a と b は再利用されません。マーク アンド クリア メソッドを使用すると、ルート ノード (オブジェクト e) から開始して、3 つのオブジェクト c、d、e が

reachable

としてマークされますが、a と b はマークできません。したがって、a と b はリサイクルされます。

这是读者可能会有疑问,为什么确定根节点是e,而不会是a、b、c、d呢?这里就有讲究了,什么样的对象会被看成是根节点呢?一般而言,根节点的选取包括(但不限于)如下几种:

  • 当前栈帧中的本地变量表中引用的对象,如各个线程被调用的方法堆栈中使用到的参数、 局部变量、 临时变量等。

  • 全局静态变量

  • ...

3 分代收集

3.1 分代收集原理

在执行垃圾回收过程中,程序会被暂停,即 stop-the-world 。这里很好理解:你妈妈在打扫房间的时候,肯定不允许你在房间内到处丢垃圾,要不然永远也无法打扫干净。

为了减少程序的暂停时间,采用 分代回收 ( Generational Collection )降低垃圾收集耗时。

分代回收基于这样的法则:

  1. 接大部分的对象生命周期短,大部分对象都是朝生夕灭。

  2. 经历越多次数的垃圾收集且活下来的对象,说明该对象越不可能是垃圾,应该越少去收集。

Python 中,对象一共有3种世代: G0 , G1 , G2 。

  1. 对象刚创建时为 G0 。

  2. 如果在一轮 GC 扫描中存活下来,则移至 G1 ,处于 G1 的对象被扫描次数会减少。

  3. 如果再次在扫描中活下来,则进入 G2 ,处于 G1 的对象被扫描次数将会更少。

3.2 触发GC时机

当某世代中分配的对象数量与被释放的对象之差达到某个阈值的时,将触发对该代的扫描。当某世代触发扫描时,比该世代年轻的世代也会触发扫描。

那么这个阈值是多少呢?我们可以通过代码查看或者修改,示例代码如下

import gc
threshold = gc.get_threshold()
print("各世代的阈值:", threshold)

# 设置各世代阈值
# gc.set_threshold(threshold0[, threshold1[, threshold2]])
gc.set_threshold(800, 20, 20)

输出结果如下:

各世代的阈值: (700, 10, 10)

以上がPython のガベージ コレクション メカニズムをマスターする方法。の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はyisu.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。