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
sが行うもう1つのことがあります。そうでない場合、list.append
はO(n)
操作alwaysになります-償却するためにO(1)
(はるかに速い!!!)過剰に割り当てます。ただし、allocatedサイズとfilledを追跡する必要がありますサイズ(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の「余分な」バイトがあります。これらはrealです。これは、__sizeof__
メソッドでは考慮されないガベージコレクタのオーバーヘッドです。これは、通常、マジックメソッドを直接使用することは想定されていないためです。この場合、それらの処理方法を知っている関数を使用します。 sys.getsizeof
(実際には __sizeof__
から返された値にGCオーバーヘッド を追加します。
サイズが実際に計算される方法を確認できるように、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)
はself
のob_type
を取得するマクロ(PyList_Type
を返す)であり、_PyObject_SIZE
はそのタイプから tp_basicsize
を取得する別のマクロです。 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
)に展開されるため、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
がある場合(可変長インスタンスがあることを意味する)、Tuple内のアイテムの数を乗算します( 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
え?さて、ここに私が説明を見つけていない奇妙な点があります。Tuple
sのtp_basicsize
は実際には次のように計算されます:
sizeof(PyTupleObject) - sizeof(PyObject *)
8
から追加のtp_basicsize
バイトが削除された理由は、私が知ることができなかったものです。 (可能な説明についてはMSeifertのコメントを参照してください)
しかし、これは基本的にあなたの特定の例の違いです。 list
sは、割り当てられた要素の数も保持するため、いつ過剰割り当てするかを決定するのに役立ちます。
さて、追加の要素が追加されると、リストは実際にO(1)追加を達成するためにこの過剰割り当てを実行します。これにより、MSeifertの回答が適切にカバーされているため、サイズが大きくなります。
MSeifertの回答はそれを広くカバーしています。シンプルに保つために、次のことを考えることができます。
Tuple
は不変です。一度設定すると、変更することはできません。そのため、そのオブジェクトに割り当てる必要があるメモリ量を事前に知っています。
list
は可変です。アイテムを追加または削除できます。そのサイズを知る必要があります(内部実装用)。必要に応じてサイズを変更します。
無料の食事はありません-これらの機能には費用がかかります。したがって、リストのメモリのオーバーヘッド。
タプルのサイズには接頭辞が付いています。つまり、タプルの初期化時にインタープリターが含まれるデータに十分なスペースを割り当て、それが終わりであり、不変(変更不可)であるのに対して、リストは可変オブジェクトであるため動的ですメモリの割り当て。したがって、リストを追加または変更するたびにスペースを割り当てないように(変更されたデータを格納してデータをコピーするのに十分なスペースを割り当て)、将来の追加、変更などのために追加のスペースを割り当てます。それを合計。