A tuple
membutuhkan lebih sedikit ruang memori dengan Python:
>>> a = (1,2,3)
>>> a.__sizeof__()
48
sedangkan list
s membutuhkan lebih banyak ruang memori:
>>> b = [1,2,3]
>>> b.__sizeof__()
64
Apa yang terjadi secara internal pada manajemen memori Python?
Jawaban:
Saya berasumsi Anda menggunakan CPython dan dengan 64bits (Saya mendapatkan hasil yang sama pada CPython 2.7 64-bit saya). Mungkin ada perbedaan dalam implementasi Python lainnya atau jika Anda memiliki Python 32-bit.
Terlepas dari implementasinya,
list
s berukuran variabel sedangkantuple
s berukuran tetap.Jadi
tuple
s dapat menyimpan elemen secara langsung di dalam struct, list di sisi lain memerlukan lapisan tipuan (ini menyimpan pointer ke elemen). Lapisan tipuan ini adalah pointer, pada sistem 64bit yang 64bit, karenanya 8bytes.Tapi ada hal lain yang bisa
list
dilakukan: Mereka mengalokasikan terlalu banyak. Jika tidaklist.append
akan menjadiO(n)
operasi selalu - untuk membuatnya diamortisasiO(1)
(jauh lebih cepat !!!) itu over-mengalokasikan. Tapi sekarang harus melacak ukuran yang dialokasikan dan ukuran yang terisi (tuple
hanya perlu menyimpan satu ukuran, karena ukuran yang dialokasikan dan diisi selalu identik). Itu berarti setiap daftar harus menyimpan "ukuran" lain yang pada sistem 64bit adalah integer 64bit, lagi-lagi 8 byte.Jadi
list
s membutuhkan setidaknya 16 byte lebih banyak memori daripadatuple
s. Mengapa saya mengatakan "setidaknya"? Karena alokasi berlebih. Alokasi berlebih berarti mengalokasikan lebih banyak ruang daripada yang dibutuhkan. Namun, jumlah alokasi berlebih bergantung pada "cara" Anda membuat daftar dan riwayat penambahan / penghapusan:Gambar-gambar
Saya memutuskan untuk membuat beberapa gambar untuk melengkapi penjelasan di atas. Mungkin ini membantu
Beginilah cara (secara skematis) disimpan dalam memori dalam contoh Anda. Saya menyoroti perbedaan dengan siklus merah (tangan bebas):
Itu sebenarnya hanya perkiraan karena
int
objek juga merupakan objek Python dan CPython bahkan menggunakan kembali bilangan bulat kecil, jadi representasi yang mungkin lebih akurat (meskipun tidak dapat dibaca) dari objek dalam memori adalah:Link yang berguna:
tuple
struct di repositori CPython untuk Python 2.7list
struct di repositori CPython untuk Python 2.7int
struct di repositori CPython untuk Python 2.7Perhatikan bahwa
__sizeof__
tidak benar-benar mengembalikan ukuran yang "benar"! Ini hanya mengembalikan ukuran nilai yang disimpan. Namun ketika Anda menggunakansys.getsizeof
hasilnya berbeda:Ada 24 byte "ekstra". Ini nyata , itulah overhead pengumpul sampah yang tidak diperhitungkan dalam
__sizeof__
metode. Itu karena Anda pada umumnya tidak seharusnya menggunakan metode ajaib secara langsung - gunakan fungsi yang mengetahui cara menanganinya, dalam hal ini:sys.getsizeof
(yang sebenarnya menambahkan overhead GC ke nilai yang dikembalikan dari__sizeof__
).sumber
list
alokasi memori stackoverflow.com/questions/40018398/…list()
atau pemahaman daftar.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
:Berikut
Py_TYPE(self)
adalah makro yang mengambilob_type
dariself
(kembaliPyList_Type
) sementara_PyObject_SIZE
adalah makro lain yang mengambiltp_basicsize
dari jenis itu.tp_basicsize
dihitung sebagai disizeof(PyListObject)
manaPyListObject
struct instance.The
PyListObject
Struktur memiliki tiga bidang: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
danob_size
) jadi24
kontribusi byte.Jadi untuk saat
res
ini adalah:atau:
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:
yaitu ukuran dari instance struct.
tuple
objek tidak mendefinisikantuple_sizeof
fungsi. Sebaliknya, mereka menggunakanobject_sizeof
untuk menghitung ukurannya:Ini, seperti untuk
list
s, mengambiltp_basicsize
dan, jika objek memiliki non-noltp_itemsize
(artinya memiliki instance dengan panjang variabel), ia mengalikan jumlah item dalam tupel (yang diterimanyaPy_SIZE
) dengantp_itemsize
.tp_basicsize
lagi menggunakan disizeof(PyTupleObject)
manaPyTupleObject
struct berisi :Jadi, tanpa elemen apa pun (yaitu,
Py_SIZE
mengembalikan0
) ukuran tupel kosong sama dengansizeof(PyTupleObject)
:Hah? Nah, inilah keanehan yang belum saya temukan penjelasannya,
tp_basicsize
darituple
s sebenarnya dihitung sebagai berikut:mengapa
8
byte tambahan dihapus daritp_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.
sumber
ob_item[1]
sebagian besar adalah placeholder (jadi masuk akal jika dikurangi dari ukuran dasar). Thetuple
dialokasikan menggunakanPyObject_NewVar
. Saya belum menemukan detailnya jadi itu hanya tebakan ...Jawaban MSeifert mencakupnya secara luas; agar tetap sederhana, Anda dapat memikirkan:
tuple
tidak dapat diubah. Setelah disetel, Anda tidak dapat mengubahnya. Jadi, Anda tahu sebelumnya berapa banyak memori yang perlu dialokasikan untuk objek itu.list
bisa berubah. Anda dapat menambah atau menghapus item ke atau dari itu. Itu harus mengetahui ukurannya (untuk impl. Internal). Ini mengubah ukuran sesuai kebutuhan.Tidak ada makanan gratis - kemampuan ini dikenakan biaya. Karenanya overhead dalam memori untuk daftar.
sumber
Ukuran tupel diawali, yang berarti pada inisialisasi tupel, interpreter mengalokasikan ruang yang cukup untuk data yang terkandung, dan itulah akhirnya, memberikannya tidak dapat diubah (tidak dapat diubah), sedangkan daftar adalah objek yang dapat berubah sehingga menyiratkan dinamika alokasi memori, jadi untuk menghindari mengalokasikan ruang setiap kali Anda menambahkan atau memodifikasi daftar (mengalokasikan cukup ruang untuk memuat data yang diubah dan menyalin datanya ke dalamnya), ia mengalokasikan ruang tambahan untuk penambahan di masa mendatang, modifikasi, ... yang cukup banyak meringkasnya.
sumber