Saya membuat dua daftar l1
dan l2
, tetapi masing-masing dengan metode pembuatan yang berbeda:
import sys
l1 = [None] * 10
l2 = [None for _ in range(10)]
print('Size of l1 =', sys.getsizeof(l1))
print('Size of l2 =', sys.getsizeof(l2))
Tapi hasilnya mengejutkan saya:
Size of l1 = 144
Size of l2 = 192
Daftar yang dibuat dengan pemahaman daftar adalah ukuran yang lebih besar dalam memori, tetapi kedua daftar tersebut identik dengan Python.
Mengapa demikian? Apakah ini masalah internal CPython, atau penjelasan lain?
python
list
memory-management
python-internals
Andrej Kesely
sumber
sumber
144 == sys.getsizeof([]) + 8*10)
mana 8 adalah ukuran pointer.10
ke11
,[None] * 11
daftar memiliki ukuran152
, tetapi pemahaman daftar masih memiliki ukuran192
. Pertanyaan yang ditautkan sebelumnya bukan duplikat yang tepat, tetapi relevan untuk memahami mengapa ini terjadi.Jawaban:
Ketika Anda menulis
[None] * 10
, Python tahu bahwa ia akan membutuhkan daftar tepat 10 objek, sehingga mengalokasikannya dengan tepat.Saat Anda menggunakan pemahaman daftar, Python tidak tahu berapa banyak yang dibutuhkan. Jadi secara bertahap tumbuh daftar sebagai elemen ditambahkan. Untuk setiap realokasi, ia mengalokasikan lebih banyak ruang daripada yang dibutuhkan segera, sehingga tidak harus merealokasi untuk setiap elemen. Daftar yang dihasilkan cenderung lebih besar dari yang dibutuhkan.
Anda dapat melihat perilaku ini saat membandingkan daftar yang dibuat dengan ukuran yang serupa:
Anda dapat melihat bahwa metode pertama mengalokasikan apa yang dibutuhkan, sedangkan metode kedua tumbuh secara berkala. Dalam contoh ini, ia mengalokasikan cukup untuk 16 elemen, dan harus realokasi ketika mencapai tanggal 17.
sumber
*
ketika saya tahu ukuran di depan.[x] * n
dengan tidak berubahx
dalam daftar Anda. Daftar yang dihasilkan akan menyimpan referensi ke objek yang identik.Seperti dicatat dalam pertanyaan ini , pemahaman daftar menggunakan di
list.append
bawah tenda, sehingga akan memanggil metode daftar-ukuran, yang secara keseluruhan dialokasikan.Untuk menunjukkan hal ini kepada diri sendiri, Anda dapat menggunakan
dis
dissasembler:Perhatikan
LIST_APPEND
opcode dalam pembongkaran<listcomp>
objek kode. Dari dokumen :Sekarang, untuk operasi pengulangan daftar, kami memiliki petunjuk tentang apa yang terjadi jika kita mempertimbangkan:
Jadi, tampaknya untuk dapat persis mengalokasikan ukuran. Melihat kode sumber , kami melihat inilah yang terjadi:
Yaitu, di sini:
size = Py_SIZE(a) * n;
. Sisa fungsi hanya mengisi array.sumber
.extend()
.list.append
adalah operasi waktu konstan diamortisasi karena ketika daftar mengubah ukuran, itu secara keseluruhan menempatkan. Tidak setiap operasi penambahan, oleh karena itu, menghasilkan array yang baru dialokasikan. Dalam kasus apa pun pertanyaan yang saya tautkan menunjukkan kepada Anda dalam kode sumber yang pada kenyataannya, pemahaman daftar memang digunakanlist.append
,. Saya akan kembali ke laptop saya sebentar lagi dan saya dapat menunjukkan kepada Anda bytecode yang dibongkar untuk pemahaman daftar danLIST_APPEND
opcode yang sesuaiTidak ada yang merupakan blok memori, tetapi itu bukan ukuran yang ditentukan sebelumnya. Selain itu, ada beberapa spasi tambahan dalam array antara elemen array. Anda dapat melihatnya sendiri dengan menjalankan:
Yang tidak total ukuran l2, tetapi lebih sedikit.
Dan ini jauh lebih besar dari sepersepuluh ukuran
l1
.Angka-angka Anda harus bervariasi tergantung pada detail sistem operasi Anda dan detail penggunaan memori saat ini di sistem operasi Anda. Ukuran [Tidak ada] tidak pernah bisa lebih besar dari memori yang berdekatan yang tersedia di mana variabel diatur untuk disimpan, dan variabel mungkin harus dipindahkan jika nanti secara dinamis dialokasikan menjadi lebih besar.
sumber
None
sebenarnya tidak disimpan dalam array yang mendasarinya, satu-satunya hal yang disimpan adalahPyObject
pointer (8 byte). Semua objek Python dialokasikan pada heap.None
adalah singleton, sehingga memiliki daftar dengan banyak nones hanya akan membuat array pointer PyObject keNone
objek yang sama di heap (dan tidak menggunakan memori tambahan dalam proses per tambahanNone
). Saya tidak yakin apa yang Anda maksud dengan "Tidak ada yang tidak memiliki ukuran yang ditentukan sebelumnya", tetapi itu kedengarannya tidak benar. Akhirnya, perulangan Anda dengangetsizeof
setiap elemen tidak menunjukkan apa yang menurut Anda itu menunjukkan.gestsizeof
pada masingele
- masingl2
adalah menyesatkan karenagetsizeof(l2)
tidak memperhitungkan ukuran elemen di dalam wadah .l1 = [None]; l2 = [None]*100; l3 = [l2]
kemudianprint(sys.getsizeof(l1), sys.getsizeof(l2), sys.getsizeof(l3))
. Anda akan mendapatkan hasil seperti:72 864 72
. Artinya, masing-masing,64 + 1*8
,64 + 100*8
, dan64 + 1*8
, sekali lagi, dengan asumsi sistem 64bit dengan ukuran pointer 8 byte.sys.getsizeof
* tidak memperhitungkan ukuran barang dalam wadah. Dari dokumen : "Hanya konsumsi memori yang secara langsung dikaitkan dengan objek diperhitungkan, bukan konsumsi memori objek yang dimaksud ... Lihat ukuran rekursif resep untuk contoh menggunakan getsizeof () secara rekursif untuk menemukan ukuran wadah dan semua isinya. "