A tuple
はPythonで必要なメモリ領域が少なくなります。
>>> a = (1,2,3)
>>> a.__sizeof__()
48
一方、list
sはより多くのメモリ領域を必要とします。
>>> b = [1,2,3]
>>> b.__sizeof__()
64
Pythonのメモリ管理で内部的に何が起こりますか?
A tuple
はPythonで必要なメモリ領域が少なくなります。
>>> a = (1,2,3)
>>> a.__sizeof__()
48
一方、list
sはより多くのメモリ領域を必要とします。
>>> b = [1,2,3]
>>> b.__sizeof__()
64
Pythonのメモリ管理で内部的に何が起こりますか?
回答:
私はあなたがCPythonと64ビットを使用していると思います(私のCPython 2.7 64ビットでも同じ結果が得られました)。他のPython実装や32ビットPythonを使用している場合は、違いがある可能性があります。
実装に関係なく、list
sは可変サイズで、tuple
sは固定サイズです。
したがって、tuple
sは要素を構造体内に直接格納できますが、リストは間接層を必要とします(要素へのポインタを格納します)。この間接層はポインターであり、64ビットシステムでは64ビット、つまり8バイトです。
ただし、別のことをlist
行う:それらは過剰に割り当てます。それ以外の場合list.append
は常にO(n)
操作になります -償却するためO(1)
(はるかに高速!!!)、割り当て超過です。しかし、割り当てられたサイズと塗りつぶされたサイズを追跡tuple
する必要があります(割り当てられたサイズと塗りつぶされたサイズは常に同一であるため、sは1つのサイズを格納するだけで済みます)。つまり、各リストには別の「サイズ」を格納する必要があります。これは、64ビットシステムでは64ビット整数で、これも8バイトです。
したがって、list
sにはtuple
s よりも少なくとも16バイト多いメモリが必要です。なぜ「少なくとも」と言ったのですか?割り当て超過のため。過剰割り当てとは、必要以上の領域を割り当てることを意味します。ただし、割り当て超過の量は、リストの作成方法と追加/削除履歴によって異なります。
>>> l = [1,2,3]
>>> l.__sizeof__()
64
>>> l.append(4) # triggers re-allocation (with over-allocation), because the original list is full
>>> l.__sizeof__()
96
>>> l = []
>>> l.__sizeof__()
40
>>> l.append(1) # re-allocation with over-allocation
>>> l.__sizeof__()
72
>>> l.append(2) # no re-alloc
>>> l.append(3) # no re-alloc
>>> l.__sizeof__()
72
>>> l.append(4) # still has room, so no over-allocation needed (yet)
>>> l.__sizeof__()
72
上記の説明に合わせていくつかの画像を作成することにしました。多分これらは役に立ちます
これは、(概略的に)例のメモリに格納される方法です。赤い(フリーハンド)サイクルとの違いを強調しました。
int
オブジェクトはPythonオブジェクトでもあり、CPythonは小さな整数も再利用するので、これは実際には単なる近似です。したがって、メモリ内のオブジェクトのおそらくより正確な表現(読めないかもしれません)は次のようになります。
役立つリンク:
__sizeof__
実際には「正しい」サイズを返さないことに注意してください!格納されている値のサイズのみを返します。ただし、使用sys.getsizeof
すると結果が異なります。
>>> import sys
>>> l = [1,2,3]
>>> t = (1, 2, 3)
>>> sys.getsizeof(l)
88
>>> sys.getsizeof(t)
72
24の「余分な」バイトがあります。これらは実際のものであり、__sizeof__
メソッドでは考慮されないガベージコレクタのオーバーヘッドです。これは、通常、マジックメソッドを直接使用することは想定されていないためです。この場合、それらの処理方法を知っている関数を使用しますsys.getsizeof
(実際には、から返される値にGCオーバーヘッドが追加されます__sizeof__
)。
list
メモリアロケーションstackoverflow.com/questions/40018398/...
list()
やリスト内包について詳しく知りたい場合に役立ちます。
サイズが実際に計算される方法を確認できるように、CPythonコードベースをさらに詳しく見ていきます。具体的な例では、割り当て超過は実行されていないため、触れません。
ここでは、64ビット値を使用します。
list
s のサイズは、次の関数から計算されますlist_sizeof
。
static PyObject *
list_sizeof(PyListObject *self)
{
Py_ssize_t res;
res = _PyObject_SIZE(Py_TYPE(self)) + self->allocated * sizeof(void*);
return PyInt_FromSsize_t(res);
}
ここでPy_TYPE(self)
グラブマクロであるob_type
のは、self
(復帰PyList_Type
)、一方で _PyObject_SIZE
別のマクロであるグラブtp_basicsize
その型から。where はインスタンス構造体tp_basicsize
として計算されます。sizeof(PyListObject)
PyListObject
PyListObject
構造は 3つのフィールドがあります。
PyObject_VAR_HEAD # 24 bytes
PyObject **ob_item; # 8 bytes
Py_ssize_t allocated; # 8 bytes
これらはそれらが何であるかを説明するコメント(私はトリミングしました)を持っています、それらを読むために上のリンクをたどってください。PyObject_VAR_HEAD
3つの8バイトフィールド(ob_refcount
、ob_type
およびob_size
)に拡張されるため、1 24
バイトが貢献します。
だから今のところres
:
sizeof(PyListObject) + self->allocated * sizeof(void*)
または:
40 + self->allocated * sizeof(void*)
リストインスタンスに割り当てられている要素がある場合。2番目の部分はそれらの寄与を計算します。self->allocated
は、その名前が示すように、割り当てられた要素の数を保持します。
要素がない場合、リストのサイズは次のように計算されます。
>>> [].__sizeof__()
40
つまり、インスタンス構造体のサイズ。
tuple
オブジェクトはtuple_sizeof
関数を定義しません。代わりに、object_sizeof
サイズの計算に使用します。
static PyObject *
object_sizeof(PyObject *self, PyObject *args)
{
Py_ssize_t res, isize;
res = 0;
isize = self->ob_type->tp_itemsize;
if (isize > 0)
res = Py_SIZE(self) * isize;
res += self->ob_type->tp_basicsize;
return PyInt_FromSsize_t(res);
}
これは、list
sのtp_basicsize
場合と同様に、オブジェクトがゼロ以外の場合tp_itemsize
(可変長のインスタンスを持つことを意味します)を取得し、(を介して取得するPy_SIZE
)タプル内の項目数をで乗算しtp_itemsize
ます。
tp_basicsize
構造体が含まれているsizeof(PyTupleObject)
場所を 再び使用しPyTupleObject
ます:
PyObject_VAR_HEAD # 24 bytes
PyObject *ob_item[1]; # 8 bytes
したがって、要素がない(つまりをPy_SIZE
返す0
)と、空のタプルのサイズは次のようになりますsizeof(PyTupleObject)
。
>>> ().__sizeof__()
24
え?さて、ここで私は、の説明を発見していない風変わりだtp_basicsize
がtuple
、以下のようにsは実際に計算されますが。
sizeof(PyTupleObject) - sizeof(PyObject *)
追加の8
バイトが削除される理由は、tp_basicsize
私が知ることができなかったものです。(可能な説明についてはMSeifertのコメントを参照してください)
しかし、これは基本的にあなたの具体的な例の違いです。list
sはまた、割り当てられた要素の数を維持します。
現在、追加の要素が追加されると、O(1)の追加を実現するために、リストは実際にこの過剰割り当てを実行します。MSeifertが彼の答えでうまくカバーしているので、これはより大きなサイズになります。
ob_item[1]
はほとんどがプレースホルダーであると信じています(そのため、basicsizeから差し引かれているのは理にかなっています)。tuple
使用して割り当てられていますPyObject_NewVar
。私は詳細を理解していませんので、それは単なる教育的な推測です...
タプルのサイズには接頭辞が付きます。つまり、タプルの初期化時に、インタプリタは含まれているデータに十分なスペースを割り当てます。これで終わりです。不変(変更不可)ですが、リストは可変オブジェクトであるため、動的メモリの割り当て。リストを追加または変更するたびにスペースが割り当てられないようにする(変更されたデータを含む十分なスペースを割り当て、そこにデータをコピーする)ため、将来の追加、変更などのために追加のスペースが割り当てられます。それを合計。