pythonメモリ管理およびゴミ回収

8998 ワード

一、pythonのメモリ管理
  • pythonの内部では、すべてのタイプが2つに分かれています.1つは単一の要素で構成され、1つは複数の要素で構成されています.異なる構造体による区分
    /* Nothing is actually declared to be a PyObject, but every pointer to
     * a Python object can be cast to a PyObject*.  This is inheritance built
     * by hand.  Similarly every pointer to a variable-size Python object can,
     * in addition, be cast to PyVarObject*.
     */
    typedef struct _object {
        _PyObject_HEAD_EXTRA //     
        Py_ssize_t ob_refcnt; //      ,  
        struct _typeobject *ob_type; //        
    } PyObject;  //     
    
    typedef struct {
        PyObject ob_base;  # PyObject   ,         ,    ,    
        Py_ssize_t ob_size; /* Number of items in variable part */ #            
    } PyVarObject; //     
    
    /* Define pointers to support a doubly-linked list of all live heap objects. */  
    //  :           
    #define _PyObject_HEAD_EXTRA 
        struct _object *_ob_next;  //         
        struct _object *_ob_prev;  //   
  • .
  • pythonコードでオブジェクトの作成またはオブジェクトへの値付け操作を行うと、メモリはオブジェクトに対して次の操作を行い、pythonでの参照カウント方式でもあります.
  • 双方向チェーンテーブルにオブジェクトが既に存在する場合、そのオブジェクトの参照カウントは1だけ加算される.
  • 双方向チェーンテーブルが存在しない場合、双方向チェーンテーブルにオブジェクトを追加し、対応する参照カウントを1に設定します.
  • オブジェクトの削除が実行すると、参照カウンタは1減算され、参照カウンタが0の場合、双方向チェーンテーブルから
  • が除去される.

    二、ゴミ回収--引用数
  • Pythonにおけるごみ回収メカニズムは,参照カウントを主とし,マーキングクリアと世代別回収を補助とする方式である.
  • オブジェクトを作成するか、オブジェクトに値を割り当て、参照カウントに1を加え、オブジェクトを削除すると、参照カウンタは1を減算します.オブジェクトの参照数が0の場合、Pythonのゴミ回収メカニズムはオブジェクトを回収し、双方向チェーンテーブルから削除します.
    a ="xiaoqi"
    b = a
    xiaoqiという文字列オブジェクトは、第1行がa変数で参照された後、そのオブジェクト参照カウントは1であり、その後、第2行でbにaを付与し、実際にはa参照のオブジェクトがbで参照され、このとき、その文字列オブジェクトの参照カウントは2である.
  • 削除オブジェクト
    a = "xiaoqi"
    b = a
    del a  #   "xiaoqi"       1
    注意:Python言語でdel文がオブジェクトを操作する場合、そのオブジェクトを直接メモリから削除するのではなく、そのオブジェクトの参照カウント-1を削除します.変数aの文字列への参照を削除するだけです.実際には文字列はメモリから削除されておらず、職場に相当するものがあります.単独で労働契約を解除しただけだ.
    >>> a = "xiaoqi"
    >>> b = a
    >>> del a
    >>> id(b)
    4572141808
    >>> id(a)
    Traceback (most recent call last):
    File , line 1, in 
    id(a)NameError: name 'a' is not defined
  • 上記の例から分かるように、xiaoqiという文字列オブジェクトは、1行目に変数aで参照され、このとき文字列オブジェクトの参照カウントは1であり、2行目に変数bで参照される.このとき、この文字列オブジェクトの参照カウントは2であり、3行目においてdel言語はa変数(ラベル)を削除し、後続のprintで見ることができる.メモリ内の実際の文字列オブジェクトは削除されず、delコマンドはその文字列オブジェクトに対する変数の参照を削除しただけなので、xiaoqiという文字列オブジェクトにとって効果は参照カウント-1
  • にすぎません.
    参照数ケース
    import sys
    class A:
        def __init__(self):
            '''     '''
            print('object born id:%s' %str(hex(id(self))))  #          
    
    def f1():
        '''           '''
        while True:
            c1=A()
            del c1
    
    def func(c):
        print('obejct refcount is: ',sys.getrefcount(c)) #getrefcount()             
    #  :getrefcount()     ,     +1,             -1
    
    if __name__ == '__main__':
       #    
        a=A()    #         +1
        func(a)  #             +1
    
        #      +1
        b=a
        func(a)
    
        #      b
        del b
        func(a)
    #     object born id:0x265c56a56d8
    obejct refcount is:  4
    obejct refcount is:  5
    obejct refcount is:  4
    #       ,                        

    参照カウント+1の場合
         ,  a=23
         ,  b=a
           ,        ,  func(a)
            ,      ,  list1=[a,a]

    参照数-1の場合
              ,  del a
                ,  a=24
               ,  func       ,func               (      )
              ,         

    注意:上記の例のように、同じ操作を繰り返す参照カウントは変更されません.func(a)は複数回実行され、カウントは変更されません. 
    魔法関数の__del__クラスの__del__魔法関数は、オブジェクトをクリーンアップするロジックをカスタマイズすることをサポートし、Python解釈器がdel言語でクラスのオブジェクトを削除すると、クラスの__del__関数が自動的に呼び出され、リロードできます.
    >>> class Ref:
    ...
    ...   def __init__(self, name):
    ...     self.name = name
    ...
    ...   def __del__(self):
    ...     print("    ")
    ...     del self.name
    >>>
    >>> r = Ref(name="xiaoqi")
    >>> print(r.name)
    xiaoqi
    >>>
    >>> del r

    オブジェクトの削除
    >>> print(r.name)
    Traceback (most recent call last):
     File "", line 1, in 
      print(r.name)
    NameError: name 'r' is not defined
    __del__魔法関数を再ロードすることで、delオブジェクトでどの操作を実行するかを柔軟に制御できます.
    三、循環引用
  • ごみ回収メカニズムでは、循環参照によってメモリが漏洩し、例
    #!/usr/bin/env python
    # -*- coding:utf-8 -*-
    import gc
    import objgraph
    
    class Foo(object):
        def __init__(self):
            self.data = None
    
    #          , :        1
    obj1 = Foo()
    obj2 = Foo()
    
    #         ,          +1, :        2
    obj1.data = obj2
    obj2.data = obj1
    
    #     ,       -1,         1。
    del obj1
    del obj2
    
    #         ,  python        :     、    、                 ,                  。
    gc.disable()
    
    #   ,              obj1 obj2           0,           。
    #   ,   Foo         2 。
    print(objgraph.count('Foo'))  #   
    
    #       ,                0,            。
    import gc
    import objgraph
    
    class Foo(object):
        def __init__(self):
            self.data = None
    
    obj1 = Foo()
    obj2 = Foo()
    
    del obj1
    del obj2
    
    gc.disable()
    print(objgraph.count('Foo'))  # 0
  • を示す.
  • gcモジュール
    enable() --         
    disable() --         
    isenabled() --         ,   true。 
    collect() --            ,           。
    get_count() --         。
    get_stats() --                
    get_referrers() --            
    get_referents() --            
  • objgraphモジュール
    objgraph      gc      :gc.get_objects(), gc.get_referents(), gc.get_referers(),              。objgraph            ,    。
    
                API
       def count(typename)
                 ,      gc.get_objects()       ,           。
    
       def by_type(typename)
                 。    ,                  
    
       def show_most_common_types(limits = 10)
               N(limits)   ,        。 《Python    》      ,         slots         
    
       def show_growth()
                        ,                  。       gc.collect(),                   。
  • ループリファレンスの問題により、メモリ内のオブジェクトが解放されなくなり、メモリが徐々に増大し、メモリが漏洩します.循環参照のためにタグクリアが発生します.
    四、ゴミ回収--タグクリア&世代別回収
    タグクリア
  • 「タグ-クリア」はpythonがループ参照の問題を解決するために現れ、list、tuple、instance、classe、dict、and functionなどの複数の要素からなるタイプで、1つのオブジェクトを作成するたびにオブジェクトを双方向チェーンテーブルに配置し、各オブジェクトには_ob_nextと_ob_prevポインタは、チェーンテーブルに取り付けるために使用されます.
  • pythonコードでは、オブジェクトの作成やオブジェクトへの値付けなどの操作を行うと、次のような操作が行われます.
  • 双方向チェーンテーブルにオブジェクトが既に存在する場合、そのオブジェクトの参照カウントは1だけ加算される.
  • 双方向チェーンテーブルが存在しない場合、双方向チェーンテーブルにオブジェクトを追加し、対応する参照カウントを1に設定します.
  • オブジェクトの削除が実行すると、参照カウンタは1減算され、参照カウンタが0の場合、双方向チェーンテーブルから
  • が除去される.

    オブジェクトが作成されるにつれて、双方向チェーンテーブル上のオブジェクトはますます多くなります.
  • 対象個数が700個を超えるとPythonインタプリタはゴミ回収を行う.
  • コードでアクティブにgcを実行する.collect()コマンドの場合、Pythonインタプリタはゴミ回収を行います.
  • import gc
     
    gc.collect()

    Pythonインタプリタはゴミ回収時にチェーンテーブル内の各オブジェクトを巡回し、ループリファレンスが存在する場合はループリファレンスのオブジェクトのリファレンスカウンタ-1が存在するとともにPythonインタプリタはカウンタが0(回収可能)と0(回収不可)に等しくない1を2つに分け、カウンタが0に等しいすべてのオブジェクトを回収する.カウンタが0でないオブジェクトを別の双方向チェーンテーブル(すなわち、世代別回収の次世代)に配置します.
    公式ドキュメント:https://docs.python.org/3/library/gc.html
    ぶんかつかいしゅう
    The GC classifies objects into three generations depending on how many collection sweeps they have survived. New objects are placed in the youngest generation (generation 0). If an object survives a collection it is moved into the next older generation. Since generation 2 is the oldest generation, objects in that generation remain there after a collection. In order to decide when to run, the collector keeps track of the number object allocations and deallocations since the last collection. When the number of allocations minus the number of deallocations exceeds threshold0, collection starts. Initially only generation 0 is examined. If generation 0 has been examined more than threshold1 times since generation 1 has been examined, then generation 1 is examined as well. Similarly, threshold2 controls the number of collections of generation 1 before collecting generation 2.
    翻訳:gcはオブジェクトを3世代に分けます.これは、それらがどれだけのコレクションクリーンアップを保存しているかによって異なります.新しいオブジェクトは、最も若い世代(0代目)に配置されます.オブジェクトがコレクション内で生き残ると、次の世代に移動します.第2世代は最も古い世代であるため、その世代のオブジェクトは収集後もそこに残っている.コレクタは、いつ実行されるかを決定するために、前回収集されてからオブジェクトの割り当てと解放の数を追跡します.割当数から解放数を減算してしきい値0を超えると収集が開始されます.最初は0代目のみチェック.第1世代をチェックしてから0世代をチェックした回数が閾値を1回超えた場合は、第1世代もチェックします.同様に、threshold 2は、第2世代を収集する前に、第1世代の集合数を制御する.
    世代別回収の出現は,タグクリアに時間がかかりすぎる原因を解決するためである.
    世代分け技術は典型的な空間で時間を変える技術である.この考え方は簡単に言えば、対象の存在時間が長ければ長いほど、ゴミではない可能性があり、収集を少なくすべきだということです.
  • という考え方は、マーキング・パージ・メカニズムによる追加の動作を低減することができる.世代分けとは、回収対象を数世代に分け、各世代はチェーンテーブル(集合)である.
  • 世代がマーキング・パージを行う時間は、世代内のオブジェクトの生存時間に比例します.
  • pythonには3世代、0世代、1世代の2世代がいます.3つのしきい値(700,10,10)があり、700はチェーンテーブルの対象個数が700に達したときにゴミ回収機構をトリガし、1つ目の10は第0世代スキャンが10回、第2世代スキャンが1回、第1世代スキャンが10回、第2世代スキャンが1回である.
  • は、10回スキャンした後も存在するオブジェクトを昇代処理する.すなわち0世代が1世代となる.
  • #            (700,10,10) ,            。
    import gc
     
    gc.set_threshold(threshold0[, threshold1[, threshold2]])