Saya akan mempelajari lebih dalam basis kode CPython sehingga kita dapat melihat bagaimana ukuran sebenarnya dihitung. Dalam contoh spesifik Anda , tidak ada alokasi berlebih yang dilakukan, jadi saya tidak akan menyentuhnya .
Saya akan menggunakan nilai 64-bit di sini, seperti Anda.
Ukuran untuk list
s dihitung dari fungsi berikut, 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);
}
Berikut Py_TYPE(self)
adalah makro yang mengambil ob_type
dari self
(kembali PyList_Type
) sementara _PyObject_SIZE
adalah makro lain yang mengambil tp_basicsize
dari jenis itu. tp_basicsize
dihitung sebagai di sizeof(PyListObject)
mana PyListObject
struct instance.
The PyListObject
Struktur memiliki tiga bidang:
PyObject_VAR_HEAD # 24 bytes
PyObject **ob_item; # 8 bytes
Py_ssize_t allocated; # 8 bytes
ini memiliki komentar (yang saya pangkas) menjelaskan apa itu, ikuti tautan di atas untuk membacanya. PyObject_VAR_HEAD
berkembang menjadi tiga bidang 8 byte ( ob_refcount
, ob_type
dan ob_size
) jadi 24
kontribusi byte.
Jadi untuk saat res
ini adalah:
sizeof(PyListObject) + self->allocated * sizeof(void*)
atau:
40 + self->allocated * sizeof(void*)
Jika contoh daftar memiliki elemen yang dialokasikan. bagian kedua menghitung kontribusinya. self->allocated
, seperti yang tersirat dari namanya, menampung jumlah elemen yang dialokasikan.
Tanpa elemen apa pun, ukuran daftar dihitung menjadi:
>>> [].__sizeof__()
40
yaitu ukuran dari instance struct.
tuple
objek tidak mendefinisikan tuple_sizeof
fungsi. Sebaliknya, mereka menggunakan object_sizeof
untuk menghitung ukurannya:
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);
}
Ini, seperti untuk list
s, mengambil tp_basicsize
dan, jika objek memiliki non-nol tp_itemsize
(artinya memiliki instance dengan panjang variabel), ia mengalikan jumlah item dalam tupel (yang diterimanya Py_SIZE
) dengan tp_itemsize
.
tp_basicsize
lagi menggunakan di sizeof(PyTupleObject)
mana PyTupleObject
struct berisi :
PyObject_VAR_HEAD # 24 bytes
PyObject *ob_item[1]; # 8 bytes
Jadi, tanpa elemen apa pun (yaitu, Py_SIZE
mengembalikan 0
) ukuran tupel kosong sama dengan sizeof(PyTupleObject)
:
>>> ().__sizeof__()
24
Hah? Nah, inilah keanehan yang belum saya temukan penjelasannya, tp_basicsize
dari tuple
s sebenarnya dihitung sebagai berikut:
sizeof(PyTupleObject) - sizeof(PyObject *)
mengapa 8
byte tambahan dihapus dari tp_basicsize
adalah sesuatu yang saya belum bisa temukan. (Lihat komentar MSeifert untuk penjelasan yang mungkin)
Tapi, pada dasarnya ini adalah perbedaan dalam contoh spesifik Anda . list
s juga menyimpan sejumlah elemen yang dialokasikan yang membantu menentukan kapan harus mengalokasikan berlebihan lagi.
Sekarang, ketika elemen tambahan ditambahkan, daftar memang melakukan alokasi berlebih ini untuk mencapai O (1) tambahan. Ini menghasilkan ukuran yang lebih besar karena MSeifert menutupi jawabannya dengan baik.