A tuple
benötigt in Python weniger Speicherplatz:
>>> a = (1,2,3)
>>> a.__sizeof__()
48
wohingegen list
s mehr Speicherplatz benötigt:
>>> b = [1,2,3]
>>> b.__sizeof__()
64
Was passiert intern in der Python-Speicherverwaltung?
A tuple
benötigt in Python weniger Speicherplatz:
>>> a = (1,2,3)
>>> a.__sizeof__()
48
wohingegen list
s mehr Speicherplatz benötigt:
>>> b = [1,2,3]
>>> b.__sizeof__()
64
Was passiert intern in der Python-Speicherverwaltung?
Antworten:
Ich gehe davon aus, dass Sie CPython und mit 64 Bit verwenden (ich habe die gleichen Ergebnisse auf meinem CPython 2.7 64-Bit erhalten). Es kann Unterschiede bei anderen Python-Implementierungen geben oder wenn Sie ein 32-Bit-Python haben.
Unabhängig von der Implementierung haben
list
s eine variable Größe, währendtuple
s eine feste Größe haben.Damit
tuple
s die Elemente direkt in der Struktur speichern können, benötigen Listen andererseits eine Indirektionsebene (sie speichert einen Zeiger auf die Elemente). Diese Indirektionsebene ist ein Zeiger auf 64-Bit-Systemen, der 64-Bit ist, also 8 Byte.Aber es gibt noch eine andere Sache, die sie
list
tun: Sie überbeanspruchen. Andernfallslist.append
wäre eineO(n)
Operation immer - um sie amortisiert zu machenO(1)
(viel schneller !!!), wird sie zu viel zugeordnet. Aber jetzt muss es die zugewiesene Größe und die gefüllte Größe verfolgen (es musstuple
nur eine Größe gespeichert werden, da zugewiesene und gefüllte Größe immer identisch sind). Das bedeutet, dass jede Liste eine andere "Größe" speichern muss, die auf 64-Bit-Systemen eine 64-Bit-Ganzzahl ist, wiederum 8 Bytes.So
list
Bedürfnis mindestens 16 Bytes mehr Speicher alstuple
s. Warum habe ich "zumindest" gesagt? Wegen der Überallokation. Überzuweisung bedeutet, dass mehr Speicherplatz als erforderlich zugewiesen wird. Die Höhe der Überzuweisung hängt jedoch davon ab, wie Sie die Liste und den Verlauf zum Anhängen / Löschen erstellen:Bilder
Ich habe beschlossen, einige Bilder zu erstellen, um die obige Erklärung zu begleiten. Vielleicht sind diese hilfreich
So wird es in Ihrem Beispiel (schematisch) im Speicher gespeichert. Ich habe die Unterschiede bei roten (Freihand-) Zyklen hervorgehoben:
int
Dies ist eigentlich nur eine Annäherung, da Objekte auch Python-Objekte sind und CPython sogar kleine Ganzzahlen wiederverwendet. Eine wahrscheinlich genauere Darstellung (obwohl nicht so lesbar) der Objekte im Speicher wäre also:Nützliche Links:
tuple
Struktur im CPython-Repository für Python 2.7list
Struktur im CPython-Repository für Python 2.7int
Struktur im CPython-Repository für Python 2.7Beachten Sie, dass
__sizeof__
die "richtige" Größe nicht wirklich zurückgegeben wird! Es wird nur die Größe der gespeicherten Werte zurückgegeben. Wenn Siesys.getsizeof
das Ergebnis verwenden, ist es jedoch anders:Es gibt 24 "zusätzliche" Bytes. Diese sind real , das ist der Garbage Collector-Overhead, der in der
__sizeof__
Methode nicht berücksichtigt wird. Das liegt daran, dass Sie im Allgemeinen keine magischen Methoden direkt verwenden sollten - verwenden Sie in diesem Fall die Funktionen, die wissen, wie man damit umgeht:sys.getsizeof
(wodurch der GC-Overhead tatsächlich zu dem von zurückgegebenen Wert hinzugefügt wird__sizeof__
).quelle
list
Speicherzuordnung stackoverflow.com/questions/40018398/…list()
oder ein Listenverständnis erfahren möchten .Ich werde tiefer in die CPython-Codebasis eintauchen, damit wir sehen können, wie die Größen tatsächlich berechnet werden. In Ihrem speziellen Beispiel wurden keine Überzuweisungen vorgenommen, daher werde ich darauf nicht eingehen .
Ich werde hier 64-Bit-Werte verwenden, so wie Sie es sind.
Die Größe für
list
s wird aus der folgenden Funktion berechnetlist_sizeof
:Hier
Py_TYPE(self)
ist ein Makro, das dasob_type
vonself
(zurückkehrtPyList_Type
)_PyObject_SIZE
erfasst, während ein anderes Makrotp_basicsize
von diesem Typ erfasst wird .tp_basicsize
wird berechnet alssizeof(PyListObject)
woPyListObject
ist die Instanzstruktur.Die
PyListObject
Struktur besteht aus drei Feldern:Diese haben Kommentare (die ich gekürzt habe), die erklären, was sie sind. Folgen Sie dem obigen Link, um sie zu lesen.
PyObject_VAR_HEAD
erweitert sich in drei 8-Byte-Felder (ob_refcount
,ob_type
undob_size
), so dass ein24
Byte-Beitrag.So ist vorerst
res
:oder:
Wenn die Listeninstanz Elemente enthält, die zugewiesen sind. Der zweite Teil berechnet ihren Beitrag.
self->allocated
enthält, wie der Name schon sagt, die Anzahl der zugewiesenen Elemente.Ohne Elemente wird die Größe von Listen wie folgt berechnet:
dh die Größe der Instanzstruktur.
tuple
Objekte definieren keinetuple_sizeof
Funktion. Stattdessenobject_sizeof
berechnen sie ihre Größe:Dies, wie für
list
s, erfasst dastp_basicsize
und, wenn das Objekt eine Nicht-Null hattp_itemsize
(was bedeutet, dass es Instanzen variabler Länge hat), multipliziert es die Anzahl der Elemente im Tupel (über das es erhältPy_SIZE
) mittp_itemsize
.tp_basicsize
verwendet wieder,sizeof(PyTupleObject)
wo diePyTupleObject
Struktur enthält :Ohne Elemente (dh
Py_SIZE
Rückgabe0
) ist die Größe der leeren Tupel also gleichsizeof(PyTupleObject)
:huh? Nun, hier ist eine Kuriosität, für die ich keine Erklärung gefunden habe. Das
tp_basicsize
vontuple
s wird tatsächlich wie folgt berechnet:Warum ein zusätzliches
8
Byte entfernttp_basicsize
wird, konnte ich nicht herausfinden. (Eine mögliche Erklärung finden Sie im Kommentar von MSeifert.)Dies ist jedoch im Grunde der Unterschied in Ihrem spezifischen Beispiel .
list
s behalten auch eine Reihe von zugewiesenen Elementen bei, um festzustellen, wann eine erneute Zuordnung erforderlich ist.Wenn nun zusätzliche Elemente hinzugefügt werden, führen Listen diese Überzuweisung tatsächlich durch, um O (1) -Anhänge zu erzielen. Dies führt zu größeren Größen, da MSeiferts Cover in seiner Antwort gut abdeckt.
quelle
ob_item[1]
ist meistens ein Platzhalter (daher ist es sinnvoll, ihn von der Basicsize abzuziehen). Dastuple
wird mit zugeordnetPyObject_NewVar
. Ich habe die Details nicht herausgefunden, also ist das nur eine fundierte Vermutung ...Die Antwort von MSeifert deckt dies weitgehend ab. Um es einfach zu halten, können Sie sich vorstellen:
tuple
ist unveränderlich. Sobald es eingestellt ist, können Sie es nicht mehr ändern. Sie wissen also im Voraus, wie viel Speicher Sie für dieses Objekt reservieren müssen.list
ist veränderlich. Sie können Elemente hinzufügen oder daraus entfernen. Es muss die Größe kennen (für interne Impl.). Die Größe wird nach Bedarf geändert.Es gibt keine kostenlosen Mahlzeiten - diese Funktionen sind mit Kosten verbunden. Daher der Overhead im Speicher für Listen.
quelle
Der Größe des Tupels wird ein Präfix vorangestellt, was bedeutet, dass der Interpreter bei der Tupelinitialisierung genügend Speicherplatz für die enthaltenen Daten reserviert. Damit ist das Ende erreicht, da es unveränderlich ist (nicht geändert werden kann), während eine Liste ein veränderliches Objekt ist und somit Dynamik impliziert Zuweisung von Speicher, um zu vermeiden, dass bei jedem Anhängen oder Ändern der Liste Speicherplatz zugewiesen wird (genügend Speicherplatz zuweisen, um die geänderten Daten aufzunehmen und die Daten darauf zu kopieren), wird zusätzlicher Speicherplatz für zukünftige Anhänge, Änderungen usw. zugewiesen fasst es zusammen.
quelle