Ich habe die magische Funktion %memit
verwendet, um die Speichernutzung zu messen:
OK, es scheint also einen Zwischenschritt zu geben, in dem xrange(n)
in eine vollständige Liste instanziiert wird. Aber was, wenn ich meine Liste in 10 Unterlisten zerschneide und sie einzeln einsammle? Dies wäre mehr Speichereffizienz, oder?
Nun, das lief nicht wie erwartet. Warum verbraucht der reduce
-Ansatz mehr Speicher als set(xrange(n))
?
Es gibt viele Missverständnisse, die geklärt werden müssen. Zunächst wird ein xrange
nicht in eine Liste umgewandelt, bevor es zu einem set
in set(xrange(n))
gemacht wird!
Der Spitzenspeicher für den reduce
-Ansatz ergibt sich aus der Tatsache, dass set.union
einen neuen Wert zurückgibt, der eine Vereinigung der 2 Ergebnismengen ist. Und reduce
speichert intern einen zusätzlichen Wert, den accum_value
. Also auf der letzten Iteration dieser for
Schleife:
Das accum_value
, das in die Funktion einfließt, enthält 90% von 10⁷ Elementen, x
enthält 10% von 10⁷ Elementen, aber der Rückgabewert benötigt Platz für alle 10⁷ Elemente. Raum für all diese Dinge muss gleichzeitig reserviert werden. Erst nachdem function
zurückgegeben wurde, wird der Speicher für die alten accum_value
und x
freigegeben.
Aber das ist noch nicht das Ende. Der Spitzenspeicher gibt an, wie viel Speicher in benötigt wird, aber nicht, wie viel verwendet wird, nachdem der Vorgang abgeschlossen wurde, wenn der Satz noch existierte.
>Somit wird ein anderer Benchmark benötigt:
%Vor%und
%Vor% Die Speicherauslastung für reduce
fällt nach der Ausführung auf 778 MiB ab; es ist jedoch immer noch mehr als für den einfacheren Fall-Aufbau-Fall. Und wie Sie sehen können, benötigt der set(xrange(n))
nicht viel internen Speicher, da die Speicherauslastung nach der Erstellung des Sets um 9 MiB abfällt.
Vor allem ist der reduce
-Ansatz nicht nur langsamer; Die resultierende Menge verbraucht außerdem doppelt so viel Speicher . Dies liegt daran, dass eine Menge durch eine Hashtabelle unterstützt wird und dass sie immer größer wird, wenn angenommen wird, dass sie zu viele Kollisionen aufweist. Sie haben pathologisches Verhalten festgestellt, bei dem die gleichen Werte dazu führen, dass einer doppelt so viel Speicher verbraucht wie der andere.
Pro% der Dokumente entspricht reduce
in etwa:
Iterieren über iterable
, (set(xrange(p, n, 10)) for p in range(10))
,
benötigt ca. 447 MiB.
Sie könnten denken, dass, da dieses iterable ein Generator-Ausdruck ist, Sie Speicher sparen werden, aber ganze Zahlen sind in einer internen freien Liste gespeichert :
"Für Geschwindigkeit" verwaltet Python eine interne freie Liste für Integer-Objekte. Leider ist diese freie Liste sowohl unsterblich als auch unbeschränkt.
Sobald also jeder Satz instanziiert wurde, wird der meiste Speicher, den er verbraucht, niemals freigegeben.
Der zurückgegebene Wert accum_value
benötigt ebenfalls ca. 447 MiB. Zusammen erfordert der Aufruf von reduce
also ungefähr 447 + 447 = 894 MiB.
Tags und Links python memory python-2.7 set