A tuple
, Python'da daha az bellek alanı kaplar:
>>> a = (1,2,3)
>>> a.__sizeof__()
48
oysa list
s 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 list
s 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, list
s değişken büyüklükte ise tuple
s sabit boyuttadır.
Böylece tuple
s öğ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 list
Onlar 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 list
s ihtiyacı en az 16 den fazla bellek bayt tuple
s. 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ü int
nesneler 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.getsizeof
sonuç 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.
list
S 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_type
bir self
(dönen PyList_Type
) ise _PyObject_SIZE
başka bir makro olduğunu kıskaçlar tp_basicsize
bu tip. örnek yapısının nerede olduğu tp_basicsize
olarak hesaplanır .sizeof(PyListObject)
PyListObject
PyListObject
Yapı üç 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_type
ve ob_size
) genişler , böylece bir 24
bayt 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.
tuple
nesneler bir tuple_sizeof
işlevi tanımlamaz . Bunun yerine, object_sizeof
boyutları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, list
s için olduğu gibi , tp_basicsize
ve 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_basicsize
yapı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_SIZE
dö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
, tuple
s aslında şu şekilde hesaplanıyor:
sizeof(PyTupleObject) - sizeof(PyObject *)
neden ek 8
baytları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 . list
s 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 tuple
tahsis edilir PyObject_NewVar
. Ayrıntıları
MSeifert cevabı bunu genel olarak kapsar; basit tutmak için şunları düşünebilirsiniz:
tuple
değ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.
list
değ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.