Python内部メカニズム-PyIntobjectオブジェクト
Python intオブジェクトの実装
Python内部のintオブジェクトの実装については、私の前の2つの文章で簡単に紹介しましたが、本稿では、前の基礎の上でintオブジェクトの内部実装をより深く分析し、Pythonがintオブジェクトを最適化するために採用したキャッシュ技術などを紹介します.まず、intオブジェクトのCレベルのデータ構造を見てみましょう.
Include/intobject.h
PyObject_HEADは1つのマクロで、これは前のいくつかの文章の中ですべて详しく分析したことがあって、だからintオブジェクトにとって最も重要なのは
intオブジェクトの初期化
Object/intobject.c
この初期化コードの情報量は大きく、本稿で分析する小整数オブジェクトのキャッシュ最適化、大整数オブジェクトプールなどの知識点が含まれています.この初期化コードは2つのことをしました.1つ目は大整数オブジェクトプールを作成することです.2つ目は、小さな整数オブジェクトのキャッシュの初期化です.詳細は、関連知識点を後で分析するときに具体的に分析します.ここでは、
次に、
Include/objimpl.h
Include/object.h
intオブジェクトの構造
関連するヘッダファイルを見てみると、Pythonは文字列、UNICDE、long、size_からこんなに多くの構造関数を提供していることがわかりました.t,Py_ssize_tなどのタイプでPyIntobjectオブジェクトを構築する.
Include/object.h各コンストラクション関数の解析を開始すると、別の興味深い点が発見されます.それは、これらのコンストラクション関数が最終的に
Object/intobject.c上のコードはいくつかの部分に分かれています.第1の部分は、入力されたivalが小さな整数の範囲内にあるかどうかを確認し、キャッシュされたPyIntobjectオブジェクトを直接返す場合は、存在しない場合は整数オブジェクトプールにPyIntobjectオブジェクトがあるかどうかを確認し、ある場合はオブジェクトプールチェーンテーブルから取り外して返し、ない場合は1つ割り当て、初期化します.賦値は最後に戻ります.しかし、このコードは私が言ったよりずっと複雑で、以下で詳しく紹介します.ここまで、1つのintオブジェクトがどのように初期化されたのか、どのようにintオブジェクトを構築するかについて紹介しました.皆さんの心の中には少なくとも1つの全局観があると信じていますが、細部の把握はまだ足りないに違いありません.前述の小整数オブジェクトキャッシュでは、大整数オブジェクトプールが疑わしい.Pythonのintオブジェクトをより深く解析する.
小整数オブジェクトキャッシュ
まずpythonプログラムを見てみましょう.このpythonプログラムでは、小さな整数オブジェクトのキャッシュメカニズムとは何かを直感的に見ることができます.
idコマンドは、メモリにあるオブジェクトの仮想アドレスを印刷するために使用されます.上のコードを使用すると、aとbが1の場合、オブジェクトのアドレスが同じであることがわかります.つまり、両者は実際にはオブジェクトであり、名前が異なるだけです.aとbが10000の場合、2つのオブジェクトのアドレスが異なることがわかります.つまり、両者は2つの異なる対象です.aとbが2,3,4であることをテストして、臨界点を探してみてください.どの範囲の場合が同じで、どの範囲の場合が異なるのか.ここを見て、上記の結果に好奇心がありますか.ここで用いられるのはいわゆる小整数キャッシュメカニズムであり、Pythonのデフォルトは、[-5257]の範囲内の整数に対して使用される小さな整数キャッシュメカニズムです.つまり、intオブジェクトの値範囲が[-5257]内であれば、グローバルに初期化されたintオブジェクトが実用的です.ソースコードに戻って検証しましょう.上記のintオブジェクトの初期化を覚えていますか.間違いなくどこですか.
Object/intobject.c
Object/intobject.c
この章で述べる
大きい整数オブジェクトプール
前述の文章から、Pythonの中ではどこでも対象であることが分かっているが、対象はC言語の面では実際にはスタックに割り当てられた構造体である.大量のオブジェクトの構造と解放は大量のメモリの申請と解放を招き、これはメモリの破片の問題を招くに違いない.Pythonではメモリの破片の問題を避けるためにオブジェクトプールを導入した.つまり、メモリの解放時にシステムに返さず、オブジェクトプールに置く.次にPythonのオブジェクトプールがどのように実現されているかを見てみましょう.
1つのオブジェクトプールはPyIntBlockタイプの単一チェーンテーブルである.PyIntBlockはN_を保存できます.INTOBJECTS個の整数オブジェクト最初はこの単鎖表が空いていました.
block_ListはPyIntBlock単一チェーンテーブルの最初のPyIntBlockを指し、最初はこの単一チェーンテーブルが空で、オブジェクトプールを使用する必要がある場合はfill_を呼び出します.free_リストはこのオブジェクトプールを作成します.次にfill_を見てみましょうfree_リストはどのように作成されたのでしょうか.
Fillを介してfree_ListはPyIntBlockを作成し、objects配列の最後の要素を返すことができる.では、PyIntBlockの使い方を見てみましょう.前述のintオブジェクト初期化とintオブジェクトの構造を覚えていますか?この二つの場所は全部使った.
この初期化コード.小さな整数キャッシュを初期化するだけでなく、PyIntBlockも作成し、PyIntBlockのobjects配列を使用して小さな整数キャッシュを初期化する.最初はfree_リストは空なので、すべてのforループの中で、最初のループの時だけfill_を通過します.free_ListはPyIntBlockオブジェクトを割り当て、free_ListはPyIntBlock中のobjects配列の最後を指し、次いで
小さい整数の場合は、小さな整数キャッシュから直接戻りを割り当てます.そうでない場合は、まずfree_を見てみましょう.リストが空かどうか、空の説明PyIntBlockが切れた場合はfill_を使用します.free_リストは1つ割り当てて初期化して返す.コードは分かりやすいです.ここまで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_Type
とPyIntObject
のob_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 + NSMALLPOSINTS
のPyIntObject
タイプの配列の上の初期化コードはまず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オブジェクトに関する知識点はもう終わりました.