Ich habe zwei Listen l1
und l2
, aber jeder mit einer anderen Erstellungsmethode:
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))
Aber die Ausgabe hat mich überrascht:
Size of l1 = 144
Size of l2 = 192
Die mit einem Listenverständnis erstellte Liste hat eine größere Größe im Speicher, ansonsten sind die beiden Listen in Python identisch.
Warum ist das so? Ist das eine CPython-interne Sache oder eine andere Erklärung?
python
list
memory-management
python-internals
Andrej Kesely
quelle
quelle
144 == sys.getsizeof([]) + 8*10)
8 die Größe eines Zeigers ist.10
zu11
die[None] * 11
Liste eine Größe152
hat, das Listenverständnis jedoch weiterhin eine Größe hat192
. Die zuvor verknüpfte Frage ist kein genaues Duplikat, aber sie ist wichtig, um zu verstehen, warum dies geschieht.Antworten:
Wenn Sie schreiben
[None] * 10
, weiß Python, dass es eine Liste mit genau 10 Objekten benötigt, also weist es genau das zu.Wenn Sie ein Listenverständnis verwenden, weiß Python nicht, wie viel es benötigt. Daher wird die Liste schrittweise erweitert, wenn Elemente hinzugefügt werden. Für jede Neuzuweisung wird mehr Platz zugewiesen, als sofort benötigt wird, sodass nicht für jedes Element eine Neuzuweisung erforderlich ist. Die resultierende Liste ist wahrscheinlich etwas größer als nötig.
Sie können dieses Verhalten sehen, wenn Sie Listen vergleichen, die mit ähnlichen Größen erstellt wurden:
Sie können sehen, dass die erste Methode genau das zuweist, was benötigt wird, während die zweite periodisch wächst. In diesem Beispiel werden 16 Elemente zugewiesen, und bei Erreichen des 17. Elements wurde eine Neuzuweisung vorgenommen.
quelle
*
wenn ich die Größe vor mir kenne.[x] * n
mit unveränderlichenx
in Ihrer Liste verwenden. Die resultierende Liste enthält Verweise auf das identische Objekt.Wie in dieser Frage erwähnt, wird das Listenverständnis
list.append
unter der Haube verwendet, sodass die Methode zur Größenänderung der Liste aufgerufen wird, die insgesamt zugeordnet wird.Um sich dies zu demonstrieren, können Sie den
dis
Dissasembler tatsächlich verwenden :Beachten Sie den
LIST_APPEND
Opcode bei der Demontage des<listcomp>
Codeobjekts. Aus den Dokumenten :Für die Listenwiederholungsoperation haben wir nun einen Hinweis darauf, was los ist, wenn wir Folgendes berücksichtigen:
Es scheint also in der Lage zu sein , die Größe genau zuzuweisen. Wenn wir uns den Quellcode ansehen, sehen wir, dass genau dies passiert:
Nämlich hier :
size = Py_SIZE(a) * n;
. Der Rest der Funktionen füllt einfach das Array.quelle
.extend()
.list.append
ist eine amortisierte Operation mit konstanter Zeit, da eine Liste bei einer Größenänderung insgesamt zugeordnet wird. Nicht jede Append-Operation führt daher zu einem neu zugewiesenen Array. In jedem Fall verknüpft die Frage , die ich zeigt Sie in den Quellcode , dass in der Tat, Listenkomprehensionen tun Einsatzlist.append
. Ich bin gleich wieder an meinem Laptop und kann Ihnen den zerlegten Bytecode für ein Listenverständnis und den entsprechendenLIST_APPEND
Opcode zeigenKeiner ist ein Speicherblock, aber keine vorgegebene Größe. Darüber hinaus gibt es in einem Array einen zusätzlichen Abstand zwischen Array-Elementen. Sie können dies selbst sehen, indem Sie Folgendes ausführen:
Was nicht die Größe von l2 ergibt, sondern weniger ist.
Und das ist viel mehr als ein Zehntel der Größe von
l1
.Ihre Nummern sollten sowohl von den Details Ihres Betriebssystems als auch von den Details der aktuellen Speichernutzung in Ihrem Betriebssystem abhängen. Die Größe von [Keine] kann niemals größer sein als der verfügbare benachbarte Speicher, in dem die Variable gespeichert werden soll, und die Variable muss möglicherweise verschoben werden, wenn sie später dynamisch zugewiesen wird, um größer zu sein.
quelle
None
wird nicht im zugrunde liegenden Array gespeichert, sondern nur mit einemPyObject
Zeiger (8 Byte). Alle Python-Objekte werden auf dem Heap zugewiesen.None
ist ein Singleton. Wenn Sie also eine Liste mit vielen Nones haben, wird einfach ein Array von PyObject-Zeigern auf dasselbeNone
Objekt auf dem Heap erstellt (und es wird kein zusätzlicher Speicher pro Prozess verwendetNone
). Ich bin mir nicht sicher, was Sie mit "Keine hat keine vorgegebene Größe" meinen, aber das klingt nicht richtig. Schließlich zeigt Ihre Schleife mitgetsizeof
jedem Element nicht, was Sie zu demonstrieren scheinen.gestsizeof
auf jedemele
derl2
ist irreführend , weilgetsizeof(l2)
nicht Rechnung trägt , die Größe der Elemente im Innern des Behälters .l1 = [None]; l2 = [None]*100; l3 = [l2]
dannprint(sys.getsizeof(l1), sys.getsizeof(l2), sys.getsizeof(l3))
. Sie erhalten ein Ergebnis wie :72 864 72
. Das heißt, jeweils64 + 1*8
,64 + 100*8
und64 + 1*8
wieder ein 64 - Bit - System mit 8 - Byte - Zeigergröße annimmt.sys.getsizeof
berücksichtigt * nicht die Größe der Elemente im Container. Aus der docs : „Es wird nur der Speicherverbrauch für direkt auf das Objekt zugeschrieben ausmacht, nicht der Speicherverbrauch von Objekten bezieht er mich auf ... Siehe rekursive sizeof Rezept für ein Beispiel für die Verwendung getsizeof () rekursiv die Größe von Containern zu finden und alle ihre Inhalte. "