A tuple, Python'da daha az bellek alanı kaplar:
>>> a = (1,2,3)
>>> a.__sizeof__()
48
oysa lists daha fazla bellek alanı kaplar:
>>> b = [1,2,3]
>>> b.__sizeof__()
64
Python bellek yönetiminde dahili olarak ne olur?
A tuple, Python'da daha az bellek alanı kaplar:
>>> a = (1,2,3)
>>> a.__sizeof__()
48
oysa lists daha fazla bellek alanı kaplar:
>>> b = [1,2,3]
>>> b.__sizeof__()
64
Python bellek yönetiminde dahili olarak ne olur?
Yanıtlar:
CPython kullandığınızı ve 64 bit kullandığınızı varsayıyorum (CPython 2.7 64-bit'imde aynı sonuçları aldım). Diğer Python uygulamalarında veya 32 bit Python'unuz varsa farklılıklar olabilir.
Ne olursa olsun uygulama, lists değişken büyüklükte ise tuples sabit boyuttadır.
Böylece tuples öğeleri doğrudan yapının içinde depolayabilir, diğer yandan listeler bir yönlendirme katmanına ihtiyaç duyar (öğelere bir işaretçi depolar). Bu yönlendirme katmanı, 64 bitlik sistemlerde 64 bit, dolayısıyla 8 bayt olan bir işaretleyicidir.
Ama bu başka bir şey var listOnlar aşırı tahsis: s yok. Aksi takdirde list.append, her zaman bir O(n)operasyon olurdu - onu amorti etmek (çok daha hızlı !!!) için fazla tahsis eder. Ancak şimdi tahsis edilen boyutu ve doldurulan boyutu takip etmesi gerekir ( yalnızca bir boyut saklaması gerekir, çünkü tahsis edilen ve doldurulan boyut her zaman aynıdır). Bu, her listenin 64 bitlik sistemlerde yine 8 bayt olmak üzere 64 bitlik bir tam sayı olan başka bir "boyut" depolaması gerektiği anlamına gelir.O(1)tuple
So lists ihtiyacı en az 16 den fazla bellek bayt tuples. Neden "en azından" dedim? Fazla tahsis yüzünden. Aşırı tahsis, gerekenden daha fazla alan tahsis ettiği anlamına gelir. Ancak, fazla tahsis miktarı, listeyi "nasıl" oluşturduğunuza ve ekleme / silme geçmişine bağlıdır:
>>> 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
Yukarıdaki açıklamaya eşlik edecek bazı resimler oluşturmaya karar verdim. Belki bunlar yardımcı olur
Örneğinizde hafızada (şematik olarak) bu şekilde saklanır. Kırmızı (serbest el) döngülerdeki farklılıkları vurguladım:
Bu aslında sadece bir yaklaşımdır çünkü intnesneler aynı zamanda Python nesneleridir ve CPython küçük tam sayıları yeniden kullanır, dolayısıyla bellekteki nesnelerin muhtemelen daha doğru bir temsili (okunabilir olmasa da) şöyle olacaktır:
Kullanışlı bağlantılar:
tuple Python 2.7 için CPython deposundaki yapılist Python 2.7 için CPython deposundaki yapıint Python 2.7 için CPython deposundaki yapıBunun __sizeof__gerçekten "doğru" boyutu döndürmediğini unutmayın ! Yalnızca saklanan değerlerin boyutunu döndürür. Ancak kullandığınızda sys.getsizeofsonuç farklıdır:
>>> import sys
>>> l = [1,2,3]
>>> t = (1, 2, 3)
>>> sys.getsizeof(l)
88
>>> sys.getsizeof(t)
72
24 "ekstra" bayt vardır. Bunlar gerçektir , __sizeof__yöntemde hesaba katılmayan çöp toplayıcı ek yükü budur . Bunun nedeni, genellikle sihirli yöntemleri doğrudan kullanmamanız gerektiğidir - bunları nasıl işleyeceğinizi bilen işlevleri kullanın, bu durumda: sys.getsizeof(aslında GC ek yükünü döndürülen değere ekler__sizeof__ ).
list()veya bir liste anlayışı hakkında daha fazla bilgi edinmek istemeniz durumunda yararlıdır .
Boyutların gerçekte nasıl hesaplandığını görebilmemiz için CPython kod tabanına daha derin bir dalış yapacağım. Belirli örnekte , bu konuda dokunmayacağım Böylece yüksek tahsisleri, yapılmamıştır .
Burada sizin gibi 64 bitlik değerler kullanacağım.
listS için boyut aşağıdaki işlevden hesaplanır 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);
}
İşte Py_TYPE(self)çekecek bir makro olduğu ob_typebir self(dönen PyList_Type) ise _PyObject_SIZEbaşka bir makro olduğunu kıskaçlar tp_basicsizebu tip. örnek yapısının nerede olduğu tp_basicsizeolarak hesaplanır .sizeof(PyListObject)PyListObject
PyListObjectYapı üç alanı vardır:
PyObject_VAR_HEAD # 24 bytes
PyObject **ob_item; # 8 bytes
Py_ssize_t allocated; # 8 bytes
bunların ne olduklarını açıklayan yorumları (kırptığım) var, bunları okumak için yukarıdaki bağlantıyı takip edin. PyObject_VAR_HEADüç 8 baytlık alana ( ob_refcount, ob_typeve ob_size) genişler , böylece bir 24bayt katkısı olur.
Yani resşimdilik:
sizeof(PyListObject) + self->allocated * sizeof(void*)
veya:
40 + self->allocated * sizeof(void*)
Liste örneğinde ayrılmış öğeler varsa. ikinci bölüm katkılarını hesaplar. self->allocated, adından da anlaşılacağı gibi, tahsis edilen elemanların sayısını tutar.
Herhangi bir öğe olmaksızın, listelerin boyutu şu şekilde hesaplanır:
>>> [].__sizeof__()
40
yani örnek yapısının boyutu.
tuplenesneler bir tuple_sizeofişlevi tanımlamaz . Bunun yerine, object_sizeofboyutlarını hesaplamak için kullanırlar :
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);
}
Bu, lists için olduğu gibi , tp_basicsizeve eğer nesne sıfırdan farklıysa tp_itemsize(değişken uzunluklu örneklere sahipse), tuple'daki (üzerinden aldığı Py_SIZE) öğelerin sayısını çarpar tp_itemsize.
tp_basicsizeyapının içerdiğisizeof(PyTupleObject) yeri tekrar kullanır :PyTupleObject
PyObject_VAR_HEAD # 24 bytes
PyObject *ob_item[1]; # 8 bytes
Dolayısıyla, herhangi bir öğe olmadan (yani, Py_SIZEdöndürür 0) boş tupleların boyutu şuna eşittir sizeof(PyTupleObject):
>>> ().__sizeof__()
24
ha? Pekala, burada bir açıklama bulamadığım bir tuhaflık var tp_basicsize, tuples aslında şu şekilde hesaplanıyor:
sizeof(PyTupleObject) - sizeof(PyObject *)
neden ek 8baytların kaldırıldığını tp_basicsizeöğrenemediğim bir şey. (Olası bir açıklama için MSeifert'in yorumuna bakın)
Ancak, bu temelde sizin özel örneğinizdeki farktır . lists ayrıca, yeniden ne zaman fazla tahsis edileceğini belirlemeye yardımcı olan bir dizi tahsis edilmiş öğeyi de tutar.
Şimdi, ek elemanlar eklendiğinde, listeler gerçekten de O (1) uzantılarını elde etmek için bu fazla tahsisi gerçekleştirir. Bu, MSeifert'in cevabında güzelce örtüştüğü için daha büyük boyutlarla sonuçlanır.
ob_item[1]çoğunlukla bir yer tutucu (bu yüzden temel boyuttan çıkarılması mantıklı). Kullanılarak tupletahsis edilir PyObject_NewVar. Ayrıntıları
MSeifert cevabı bunu genel olarak kapsar; basit tutmak için şunları düşünebilirsiniz:
tupledeğişmezdir. Bir kez ayarlandıktan sonra değiştiremezsiniz. Böylece o nesne için ne kadar bellek ayırmanız gerektiğini önceden bilirsiniz.
listdeğiştirilebilir. İçine veya oradan öğe ekleyebilir veya çıkarabilirsiniz. Bunun boyutunu bilmek zorundadır (iç uygulama için). Gerektiği gibi yeniden boyutlandırılır.
Bedava yemek yoktur - bu yetenekler bir ücrete tabidir. Bu nedenle listeler için hafızadaki ek yük.
Tuple'ın boyutu önceden eklenmiştir, yani tuple başlatıldığında yorumlayıcı içerilen veriler için yeterli alan ayırır ve bu onun sonudur, değişmez verir (değiştirilemez), oysa bir liste değişken bir nesnedir, dolayısıyla dinamiktir. bellek tahsisi, böylece listeyi her eklediğinizde veya değiştirdiğinizde alan tahsis etmekten kaçınmak için (değiştirilen verileri içermek ve verileri ona kopyalamak için yeterli alan ayırın), gelecekteki ekleme, değişiklikler vb. için ek alan ayırır. özetliyor.