Also habe ich mit list
Objekten gespielt und wenig Seltsames gefunden, das, wenn list
es damit erstellt list()
wird, mehr Speicher benötigt als Listenverständnis? Ich benutze Python 3.5.2
In [1]: import sys
In [2]: a = list(range(100))
In [3]: sys.getsizeof(a)
Out[3]: 1008
In [4]: b = [i for i in range(100)]
In [5]: sys.getsizeof(b)
Out[5]: 912
In [6]: type(a) == type(b)
Out[6]: True
In [7]: a == b
Out[7]: True
In [8]: sys.getsizeof(list(b))
Out[8]: 1008
Aus den Dokumenten :
Listen können auf verschiedene Arten erstellt werden:
- Verwenden Sie ein Paar eckige Klammern, um die leere Liste zu kennzeichnen:
[]
- Trennen Sie Elemente in eckigen Klammern durch Kommas :
[a]
,[a, b, c]
- Verwenden eines Listenverständnisses:
[x for x in iterable]
- Verwenden des Typkonstruktors:
list()
oderlist(iterable)
Aber es scheint, dass die list()
Verwendung mehr Speicher benötigt.
Und je viel list
größer ist, desto größer wird der Abstand.
Warum passiert das?
UPDATE # 1
Test mit Python 3.6.0b2:
Python 3.6.0b2 (default, Oct 11 2016, 11:52:53)
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.getsizeof(list(range(100)))
1008
>>> sys.getsizeof([i for i in range(100)])
912
UPDATE # 2
Test mit Python 2.7.12:
Python 2.7.12 (default, Jul 1 2016, 15:12:24)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import sys
>>> sys.getsizeof(list(xrange(100)))
1016
>>> sys.getsizeof([i for i in xrange(100)])
920
python
list
list-comprehension
cpython
python-internals
vishes_shell
quelle
quelle
sys.getsizeof(list(range(100)))
ist 1016,getsizeof(range(100))
ist 872 undgetsizeof([i for i in range(100)])
ist 920. Alle haben den Typlist
.xrange
.append()
, liegt möglicherweise eine Überbelegung des Speichers vor. Ich denke, der einzige Weg, dies wirklich zu klären, besteht darin, sich die Python-Quellen anzusehen.Antworten:
Ich denke, Sie sehen Überzuordnungsmuster. Dies ist ein Beispiel aus der Quelle :
/* This over-allocates proportional to the list size, making room * for additional growth. The over-allocation is mild, but is * enough to give linear-time amortized behavior over a long * sequence of appends() in the presence of a poorly-performing * system realloc(). * The growth pattern is: 0, 4, 8, 16, 25, 35, 46, 58, 72, 88, ... */ new_allocated = (newsize >> 3) + (newsize < 9 ? 3 : 6);
Wenn Sie die Größen des Listenverständnisses der Längen 0-88 drucken, sehen Sie die Musterübereinstimmungen:
# create comprehensions for sizes 0-88 comprehensions = [sys.getsizeof([1 for _ in range(l)]) for l in range(90)] # only take those that resulted in growth compared to previous length steps = zip(comprehensions, comprehensions[1:]) growths = [x for x in list(enumerate(steps)) if x[1][0] != x[1][1]] # print the results: for growth in growths: print(growth)
Ergebnisse (Format ist
(list length, (old total size, new total size))
):(0, (64, 96)) (4, (96, 128)) (8, (128, 192)) (16, (192, 264)) (25, (264, 344)) (35, (344, 432)) (46, (432, 528)) (58, (528, 640)) (72, (640, 768)) (88, (768, 912))
Die Überzuweisung erfolgt aus Leistungsgründen, sodass Listen wachsen können, ohne mit jedem Wachstum mehr Speicher zuzuweisen (bessere amortisierte Leistung).
Ein wahrscheinlicher Grund für den Unterschied bei der Verwendung des Listenverständnisses ist, dass das Listenverständnis die Größe der generierten Liste nicht deterministisch berechnen
list()
kann , sondern kann. Dies bedeutet, dass das Verständnis die Liste kontinuierlich erweitert, während es mit Überzuweisung gefüllt wird, bis es schließlich gefüllt wird.Es ist möglich, dass der Überzuweisungspuffer mit nicht verwendeten zugewiesenen Knoten nicht vergrößert wird, sobald dies erledigt ist (in den meisten Fällen wird dies den Zweck der Überzuweisung nicht zunichte machen).
list()
Es kann jedoch unabhängig von der Listengröße ein Puffer hinzugefügt werden, da die endgültige Listengröße im Voraus bekannt ist.Ein weiterer Beleg, ebenfalls aus der Quelle, ist, dass Listenverständnisse aufgerufen werden
LIST_APPEND
, die auf die Verwendung von hinweisenlist.resize
, was wiederum darauf hinweist, dass der Puffer vor der Zuweisung verbraucht wird, ohne zu wissen, wie viel davon gefüllt wird. Dies stimmt mit dem Verhalten überein, das Sie sehen.Abschließend
list()
wird mehr Knoten in Abhängigkeit von der Liste Größe vorbelegt>>> sys.getsizeof(list([1,2,3])) 60 >>> sys.getsizeof(list([1,2,3,4])) 64
Das Listenverständnis kennt die Listengröße nicht und verwendet daher Anhängeoperationen, wenn es wächst, wodurch der Puffer für die Vorzuweisung aufgebraucht wird:
# one item before filling pre-allocation buffer completely >>> sys.getsizeof([i for i in [1,2,3]]) 52 # fills pre-allocation buffer completely # note that size did not change, we still have buffered unused nodes >>> sys.getsizeof([i for i in [1,2,3,4]]) 52 # grows pre-allocation buffer >>> sys.getsizeof([i for i in [1,2,3,4,5]]) 68
quelle
list.resize
. Ich bin kein Experte für die Navigation durch die Quelle, aber wenn einer die Größe ändert und der andere nicht, könnte dies den Unterschied erklären.64, 96, 104, 112, 120, 128, 136, 144, 160, 192, 200, 208, 216, 224, 232, 240, 256, 264, 272, 280, 288, 296, 304, 312, 328, 336, 344, 352, 360, 368, 376, 384, 400, 408, 416
und zum Verständnis64, 96, 96, 96, 96, 128, 128, 128, 128, 192, 192, 192, 192, 192, 192, 192, 192, 264, 264, 264, 264, 264, 264, 264, 264, 264, 344, 344, 344, 344, 344, 344, 344, 344, 344
. Ich würde davon ausgehen, dass dieses Verständnis derjenige ist, der den Speicher als Algorithmus zuzuweisen scheint, der für bestimmte Größen mehr RAM verwendet.list()
bestimmt determinisListenGröße, die Liste Verständnis nicht tun kann. Dies deutet darauf hin, dass das Listenverständnis nicht immer das "letzte" Wachstum der Liste "auslöst". Könnte Sinn machen.Vielen Dank an alle, die mir geholfen haben, dieses großartige Python zu verstehen.
Ich möchte keine so massiven Fragen stellen (deshalb poste ich eine Antwort), sondern nur meine Gedanken zeigen und teilen.
Wie @ReutSharabani richtig bemerkte: "list () bestimmt deterministisch die Listengröße ". Sie können es aus dieser Grafik sehen.
Wenn Sie
append
oder Listenverständnis verwenden, haben Sie immer eine Art von Grenzen, die sich erweitern, wenn Sie einen Punkt erreichen. Und mitlist()
dir haben fast die gleichen Grenzen, aber sie schweben.AKTUALISIEREN
Also danke an @ReutSharabani , @tavo , @SvenFestersen
Zusammenfassend
list()
lässt sich sagen, dass die Vorbelegung des Speichers von der Listengröße abhängt. Das Listenverständnis kann dies nicht (es fordert bei Bedarf mehr Speicher an.append()
). Deshalblist()
Speicher mehr Speicher.Ein weiteres Diagramm, das die
list()
Vorbelegung des Speichers zeigt. Die grüne Linie zeigt also daslist(range(830))
Anhängen von Element zu Element und für eine Weile ändert sich der Speicher nicht.UPDATE 2
Wie @Barmar in den Kommentaren unten bemerkte,
list()
muss ich schneller als das Listenverständnis sein, also bin ichtimeit()
mitnumber=1000
einer Länge vonlist
von4**0
bis gelaufen4**10
und die Ergebnisse sindquelle
list
Konstruktor , wenn er die Größe der neuen Liste anhand seines Arguments bestimmen kann, immer noch dieselbe Menge an Speicherplatz zuweist, als wenn das letzte Element gerade dort angekommen wäre und nicht genügend Speicherplatz dafür vorhanden wäre. Zumindest macht das für mich Sinn.range
Objekt durchführen (das könnte Spaß machen).