A tupleはPythonで必要なメモリ領域が少なくなります。
>>> a = (1,2,3)
>>> a.__sizeof__()
48
一方、listsはより多くのメモリ領域を必要とします。
>>> b = [1,2,3]
>>> b.__sizeof__()
64
Pythonのメモリ管理で内部的に何が起こりますか?
A tupleはPythonで必要なメモリ領域が少なくなります。
>>> a = (1,2,3)
>>> a.__sizeof__()
48
一方、listsはより多くのメモリ領域を必要とします。
>>> b = [1,2,3]
>>> b.__sizeof__()
64
Pythonのメモリ管理で内部的に何が起こりますか?
回答:
私はあなたがCPythonと64ビットを使用していると思います(私のCPython 2.7 64ビットでも同じ結果が得られました)。他のPython実装や32ビットPythonを使用している場合は、違いがある可能性があります。
実装に関係なく、listsは可変サイズで、tuplesは固定サイズです。
したがって、tuplesは要素を構造体内に直接格納できますが、リストは間接層を必要とします(要素へのポインタを格納します)。この間接層はポインターであり、64ビットシステムでは64ビット、つまり8バイトです。
ただし、別のことをlist行う:それらは過剰に割り当てます。それ以外の場合list.appendは常にO(n)操作になります -償却するためO(1)(はるかに高速!!!)、割り当て超過です。しかし、割り当てられたサイズと塗りつぶされたサイズを追跡tupleする必要があります(割り当てられたサイズと塗りつぶされたサイズは常に同一であるため、sは1つのサイズを格納するだけで済みます)。つまり、各リストには別の「サイズ」を格納する必要があります。これは、64ビットシステムでは64ビット整数で、これも8バイトです。
したがって、listsにはtuples よりも少なくとも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ビット値を使用します。
lists のサイズは、次の関数から計算されます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_HEAD3つの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);
}
これは、listsの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のコメントを参照してください)
しかし、これは基本的にあなたの具体的な例の違いです。listsはまた、割り当てられた要素の数を維持します。
現在、追加の要素が追加されると、O(1)の追加を実現するために、リストは実際にこの過剰割り当てを実行します。MSeifertが彼の答えでうまくカバーしているので、これはより大きなサイズになります。
ob_item[1]はほとんどがプレースホルダーであると信じています(そのため、basicsizeから差し引かれているのは理にかなっています)。tuple使用して割り当てられていますPyObject_NewVar。私は詳細を理解していませんので、それは単なる教育的な推測です...
タプルのサイズには接頭辞が付きます。つまり、タプルの初期化時に、インタプリタは含まれているデータに十分なスペースを割り当てます。これで終わりです。不変(変更不可)ですが、リストは可変オブジェクトであるため、動的メモリの割り当て。リストを追加または変更するたびにスペースが割り当てられないようにする(変更されたデータを含む十分なスペースを割り当て、そこにデータをコピーする)ため、将来の追加、変更などのために追加のスペースが割り当てられます。それを合計。