Python内部メカニズム-PyIntobjectオブジェクト


Python intオブジェクトの実装
Python内部のintオブジェクトの実装については、私の前の2つの文章で簡単に紹介しましたが、本稿では、前の基礎の上でintオブジェクトの内部実装をより深く分析し、Pythonがintオブジェクトを最適化するために採用したキャッシュ技術などを紹介します.まず、intオブジェクトのCレベルのデータ構造を見てみましょう.
typedef struct {             
    PyObject_HEAD            
    long ob_ival;            
} PyIntObject; 

Include/intobject.h
PyObject_HEADは1つのマクロで、これは前のいくつかの文章の中ですべて详しく分析したことがあって、だからintオブジェクトにとって最も重要なのはob_ivalメンバーで、このメンバーは実际に保存したデータを保存するために使います.この基础があれば私达はPythonソースコードの中でローミングを始めることができます.まずintオブジェクトがどんな初期化の动作をしたことを见てみましょう.
intオブジェクトの初期化
int                       
_PyInt_Init(void)                                                                                                                                                         
{                         
    PyIntObject *v;       
    int ival;             
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
    for (ival = -NSMALLNEGINTS; ival < NSMALLPOSINTS; ival++) {
          if (!free_list && (free_list = fill_free_list()) == NULL)
                    return 0;
        /* PyObject_New is inlined */
        v = free_list;    
        free_list = (PyIntObject *)Py_TYPE(v);
        PyObject_INIT(v, &PyInt_Type);
        v->ob_ival = ival;
        small_ints[ival + NSMALLNEGINTS] = v; 
    }                     
#endif 
    return 1;             
} 

Object/intobject.c
この初期化コードの情報量は大きく、本稿で分析する小整数オブジェクトのキャッシュ最適化、大整数オブジェクトプールなどの知識点が含まれています.この初期化コードは2つのことをしました.1つ目は大整数オブジェクトプールを作成することです.2つ目は、小さな整数オブジェクトのキャッシュの初期化です.詳細は、関連知識点を後で分析するときに具体的に分析します.ここでは、PyObject_INITを重点的に見てみましょう.これは、マクロが初期化タイプオブジェクトに特化しているものです.上記の初期化コードではvはPyIntObjectタイプであるが、まだ初期化は行われていない.PyObject_INITマクロによりPyIntObjectオブジェクトとこのオブジェクトに対応するタイプPyInt_Typeを関連付ける.これも前述の記事で述べたように、オブジェクトは初期化時にそのタイプを固定します.PyInt_TypeはintオブジェクトのためにカスタマイズされたPyTypeObject(このオブジェクトが何をしているのか忘れたら、私の前の文章を見てください)オブジェクトです.
次に、PyObject_INITマクロの具体的な実装を示す.
#define PyObject_INIT(op, typeobj) \
    ( Py_TYPE(op) = (typeobj), _Py_NewReference((PyObject *)(op)), (op) )

Include/objimpl.h
#define Py_TYPE(ob) (((PyObject*)(ob))->ob_type)

Include/object.h PyObject_INITの実装は簡単で、PyInt_TypePyIntObjectob_typeメンバーを関連付けることです.これでPyIntObjectオブジェクトの初期化が完了し、参照カウントが追加されます.これで初期化は終わり、初期化ということで、intペアをどのように作成するかということになります.
intオブジェクトの構造
関連するヘッダファイルを見てみると、Pythonは文字列、UNICDE、long、size_からこんなに多くの構造関数を提供していることがわかりました.t,Py_ssize_tなどのタイプでPyIntobjectオブジェクトを構築する.
PyAPI_FUNC(PyObject *) PyInt_FromString(char*, char**, int);
#ifdef Py_USING_UNICODE
PyAPI_FUNC(PyObject *) PyInt_FromUnicode(Py_UNICODE*, Py_ssize_t, int);
#endif              
PyAPI_FUNC(PyObject *) PyInt_FromLong(long);
PyAPI_FUNC(PyObject *) PyInt_FromSize_t(size_t);
PyAPI_FUNC(PyObject *) PyInt_FromSsize_t(Py_ssize_t);

Include/object.h各コンストラクション関数の解析を開始すると、別の興味深い点が発見されます.それは、これらのコンストラクション関数が最終的にPyInt_FromLongという関数を呼び出し、コンストラクション関数が大きく異なることです.そこで、次に、PyInt_FromLong関数の実装を分析することでintオブジェクトの構築方法を学びます.
PyObject *
PyInt_FromLong(long ival)
{
    register PyIntObject *v;
//Setp1:   ival        ,           PyIntObject  
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
    if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) {
        v = small_ints[ival + NSMALLNEGINTS];
        Py_INCREF(v);
#ifdef COUNT_ALLOCS
        if (ival >= 0)
            quick_int_allocs++;
        else 
            quick_neg_int_allocs++;
#endif
        return (PyObject *) v;
    }
#endif
//Setp2:         PyIntObject  ,      fill_free_list      
    if (free_list == NULL) {
        if ((free_list = fill_free_list()) == NULL)
            return NULL;
    }
//Setp3:    PyIntObject,    ,    .
    /* Inline PyObject_New */
    v = free_list;
    free_list = (PyIntObject *)Py_TYPE(v);
    PyObject_INIT(v, &PyInt_Type);
    v->ob_ival = ival;
    return (PyObject *) v;
}

Object/intobject.c上のコードはいくつかの部分に分かれています.第1の部分は、入力されたivalが小さな整数の範囲内にあるかどうかを確認し、キャッシュされたPyIntobjectオブジェクトを直接返す場合は、存在しない場合は整数オブジェクトプールにPyIntobjectオブジェクトがあるかどうかを確認し、ある場合はオブジェクトプールチェーンテーブルから取り外して返し、ない場合は1つ割り当て、初期化します.賦値は最後に戻ります.しかし、このコードは私が言ったよりずっと複雑で、以下で詳しく紹介します.ここまで、1つのintオブジェクトがどのように初期化されたのか、どのようにintオブジェクトを構築するかについて紹介しました.皆さんの心の中には少なくとも1つの全局観があると信じていますが、細部の把握はまだ足りないに違いありません.前述の小整数オブジェクトキャッシュでは、大整数オブジェクトプールが疑わしい.Pythonのintオブジェクトをより深く解析する.
小整数オブジェクトキャッシュ
まずpythonプログラムを見てみましょう.このpythonプログラムでは、小さな整数オブジェクトのキャッシュメカニズムとは何かを直感的に見ることができます.
>>> a = 1
>>> b = 1
>>> id(a)
35664216
>>> id(b)
35664216
>>> a = 10000
>>> b = 10000
>>> id(a)
35946744
>>> id(b)
35946624

idコマンドは、メモリにあるオブジェクトの仮想アドレスを印刷するために使用されます.上のコードを使用すると、aとbが1の場合、オブジェクトのアドレスが同じであることがわかります.つまり、両者は実際にはオブジェクトであり、名前が異なるだけです.aとbが10000の場合、2つのオブジェクトのアドレスが異なることがわかります.つまり、両者は2つの異なる対象です.aとbが2,3,4であることをテストして、臨界点を探してみてください.どの範囲の場合が同じで、どの範囲の場合が異なるのか.ここを見て、上記の結果に好奇心がありますか.ここで用いられるのはいわゆる小整数キャッシュメカニズムであり、Pythonのデフォルトは、[-5257]の範囲内の整数に対して使用される小さな整数キャッシュメカニズムです.つまり、intオブジェクトの値範囲が[-5257]内であれば、グローバルに初期化されたintオブジェクトが実用的です.ソースコードに戻って検証しましょう.上記のintオブジェクトの初期化を覚えていますか.間違いなくどこですか.
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
    for (ival = -NSMALLNEGINTS; ival < NSMALLPOSINTS; ival++) {
          if (!free_list && (free_list = fill_free_list()) == NULL)
                    return 0;
        /* PyObject_New is inlined */
        v = free_list;    
        free_list = (PyIntObject *)Py_TYPE(v);
        PyObject_INIT(v, &PyInt_Type);
        v->ob_ival = ival;
        small_ints[ival + NSMALLNEGINTS] = v; 
    }                     
#endif 

Object/intobject.c
#ifndef NSMALLPOSINTS
#define NSMALLPOSINTS 257
#endif 
#ifndef NSMALLNEGINTS
#define NSMALLNEGINTS 5
#endif 
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
/* References to small integers are saved in this array so that they can be shared. The integers that are saved are those in the range -NSMALLNEGINTS (inclusive) to NSMALLPOSINTS (not inclusive). */        
static PyIntObject *small_ints[NSMALLNEGINTS + NSMALLPOSINTS];
#endif

Object/intobject.c NSMALLNEGINTSは5,NSMALLPOSINTSは257,small_intsは1つのサイズがNSMALLNEGINTS + NSMALLPOSINTSPyIntObjectタイプの配列の上の初期化コードはまず1つのforループであり、-NSMALLNEGINTSからNSMALLPOSINTSまで遍歴し、遍歴するたびにfill_free_list()を通じて1つのPyIntObjectオブジェクトを作成し、その後vを使用してこの作成されたばかりのPyIntObjectオブジェクトv = free_list;を指す.次に、このPyIntObjectオブジェクトPyObject_INIT(v, &PyInt_Type);v->ob_ival = ival;を初期化し、最後にvでグローバルなオブジェクト配列を初期化しますsmall_ints[ival + NSMALLNEGINTS] = v;ここまで読んで、私は皆さんがすでに小整数のキャッシュメカニズムについて一定の理解を持っていると信じています.
//Setp1:   ival        ,           PyIntObject  
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
    if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) {
        v = small_ints[ival + NSMALLNEGINTS];
        Py_INCREF(v);
#ifdef COUNT_ALLOCS
        if (ival >= 0)
            quick_int_allocs++;
        else 
            quick_neg_int_allocs++;
#endif
        return (PyObject *) v;
    }
#endif

この章で述べるfree_list,fill_free_list()等については、本稿で議論するもう一つの知識点である大整数キャッシュメカニズムであり、ここでは議論せず、引き続き下を見る.
大きい整数オブジェクトプール
前述の文章から、Pythonの中ではどこでも対象であることが分かっているが、対象はC言語の面では実際にはスタックに割り当てられた構造体である.大量のオブジェクトの構造と解放は大量のメモリの申請と解放を招き、これはメモリの破片の問題を招くに違いない.Pythonではメモリの破片の問題を避けるためにオブジェクトプールを導入した.つまり、メモリの解放時にシステムに返さず、オブジェクトプールに置く.次にPythonのオブジェクトプールがどのように実現されているかを見てみましょう.
struct _intblock {                                           
    struct _intblock *next;                                  
    PyIntObject objects[N_INTOBJECTS];                       
};                                                           

typedef struct _intblock PyIntBlock; 

1つのオブジェクトプールはPyIntBlockタイプの単一チェーンテーブルである.PyIntBlockはN_を保存できます.INTOBJECTS個の整数オブジェクト最初はこの単鎖表が空いていました.
static PyIntBlock *block_list = NULL;
static PyIntObject *free_list = NULL;

block_ListはPyIntBlock単一チェーンテーブルの最初のPyIntBlockを指し、最初はこの単一チェーンテーブルが空で、オブジェクトプールを使用する必要がある場合はfill_を呼び出します.free_リストはこのオブジェクトプールを作成します.次にfill_を見てみましょうfree_リストはどのように作成されたのでしょうか.
static PyIntObject *                   
fill_free_list(void)                   
{                                      
    PyIntObject *p, *q;                 
    /* Python's object allocator isn't appropriate for large blocks. */
    //Setp1:     sizeof(PyIntBlock)     .
    p = (PyIntObject *) PyMem_MALLOC(sizeof(PyIntBlock));
    //         
    if (p == NULL)                      
        return (PyIntObject *) PyErr_NoMemory();
    //Setp2:     block_list      
    ((PyIntBlock *)p)->next = block_list;
    //block_list          
    block_list = (PyIntBlock *)p;                        
    /* Link the int objects together, from rear to front, then return
       the address of the last int object in the block. */
    p = &((PyIntBlock *)p)->objects[0];                                                                                                                   
    q = p + N_INTOBJECTS;
    //setp3:       , PyIntBlock  objects        ,      PyIntObject   ob_type       PyIntObject   ob_type     
    //   linux                  .
    while (--q > p)                    
        Py_TYPE(q) = (struct _typeobject *)(q-1); //struct _typeobject  PyTypeObject,Py_TYPE   q ob_type  
    Py_TYPE(q) = NULL; 
    //setp3:     PyIntBlock  objects         . 
    return p + N_INTOBJECTS - 1;       
} 

Fillを介してfree_ListはPyIntBlockを作成し、objects配列の最後の要素を返すことができる.では、PyIntBlockの使い方を見てみましょう.前述のintオブジェクト初期化とintオブジェクトの構造を覚えていますか?この二つの場所は全部使った.
int
_PyInt_Init(void)
{
    PyIntObject *v;
    int ival;
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
    for (ival = -NSMALLNEGINTS; ival < NSMALLPOSINTS; ival++) {
          if (!free_list && (free_list = fill_free_list()) == NULL)
                    return 0;
        /* PyObject_New is inlined */
        v = free_list;
        free_list = (PyIntObject *)Py_TYPE(v);
        PyObject_INIT(v, &PyInt_Type);
        v->ob_ival = ival;
        small_ints[ival + NSMALLNEGINTS] = v; 
    }
#endif
    return 1;
} 

この初期化コード.小さな整数キャッシュを初期化するだけでなく、PyIntBlockも作成し、PyIntBlockのobjects配列を使用して小さな整数キャッシュを初期化する.最初はfree_リストは空なので、すべてのforループの中で、最初のループの時だけfill_を通過します.free_ListはPyIntBlockオブジェクトを割り当て、free_ListはPyIntBlock中のobjects配列の最後を指し、次いで(PyIntObject *)Py_TYPE(v)を介して次のPyIntObjectオブジェクトを指す.ここでは初期化時にPyIntBlockを用いたが、構造時にPyIntBlockオブジェクトをどのように使用するかを見てみよう.
PyObject *     
PyInt_FromLong(long ival)
{                                                                                                                                                                             
    register PyIntObject *v;
#if NSMALLNEGINTS + NSMALLPOSINTS > 0
    if (-NSMALLNEGINTS <= ival && ival < NSMALLPOSINTS) {
        v = small_ints[ival + NSMALLNEGINTS];
        Py_INCREF(v);
#ifdef COUNT_ALLOCS
        if (ival >= 0)
            quick_int_allocs++;
        else   
            quick_neg_int_allocs++;
#endif 
        return (PyObject *) v;
    }          
#endif 
    if (free_list == NULL) {
        if ((free_list = fill_free_list()) == NULL)
            return NULL;
    }          
    /* Inline PyObject_New */
    v = free_list;
    free_list = (PyIntObject *)Py_TYPE(v);
    PyObject_INIT(v, &PyInt_Type);
    v->ob_ival = ival;
    return (PyObject *) v;
}  

小さい整数の場合は、小さな整数キャッシュから直接戻りを割り当てます.そうでない場合は、まずfree_を見てみましょう.リストが空かどうか、空の説明PyIntBlockが切れた場合はfill_を使用します.free_リストは1つ割り当てて初期化して返す.コードは分かりやすいです.ここまでintオブジェクトに関する知識点はもう終わりました.