Das range(10**6)
zehnmalige Kopieren einer gemischten Liste dauert ungefähr 0,18 Sekunden: (Dies sind fünf Läufe.)
0.175597017661
0.173731403198
0.178601711594
0.180330912952
0.180811964451
Das zehnmalige Kopieren der nicht gemischten Liste dauert ungefähr 0,05 Sekunden:
0.058402235973
0.0505464636856
0.0509734306934
0.0526022752744
0.0513324916184
Hier ist mein Testcode:
from timeit import timeit
import random
a = range(10**6)
random.shuffle(a) # Remove this for the second test.
a = list(a) # Just an attempt to "normalize" the list.
for _ in range(5):
print timeit(lambda: list(a), number=10)
Ich habe auch versucht, mit zu kopieren a[:]
, die Ergebnisse waren ähnlich (dh großer Geschwindigkeitsunterschied)
Warum der große Geschwindigkeitsunterschied? Ich kenne und verstehe den Geschwindigkeitsunterschied im berühmten Warum ist es schneller, ein sortiertes Array zu verarbeiten als ein unsortiertes Array? Beispiel, aber hier hat meine Verarbeitung keine Entscheidungen. Es wird nur blind die Referenzen in der Liste kopiert, nein?
Ich verwende Python 2.7.12 unter Windows 10.
Bearbeiten: Versuchte jetzt auch Python 3.5.2, die Ergebnisse waren fast gleich (konsistent gemischt um 0,17 Sekunden, konsistent gemischt um 0,05 Sekunden). Hier ist der Code dafür:
a = list(range(10**6))
random.shuffle(a)
a = list(a)
for _ in range(5):
print(timeit(lambda: list(a), number=10))
quelle
0.25
in jeder Iteration jedes einzelnen Tests. Auf meiner Plattform spielt die Reihenfolge also eine Rolle.Antworten:
Das Interessante ist, dass es von der Reihenfolge abhängt, in der die Ganzzahlen zuerst erstellt werden. Zum Beispiel anstatt
shuffle
eine zufällige Sequenz zu erstellen mitrandom.randint
:Dies ist so schnell wie das Kopieren Ihres
list(range(10**6))
(erstes und schnelles Beispiel).Wenn Sie jedoch mischen - dann sind Ihre Ganzzahlen nicht mehr in der Reihenfolge, in der sie zuerst erstellt wurden. Das macht es langsam.
Ein kurzes Intermezzo:
Py_INCREF
inlist_slice
), sodass Python wirklich dorthin gehen muss, wo sich das Objekt befindet. Es kann nicht einfach die Referenz kopieren.Wenn Sie also Ihre Liste kopieren, erhalten Sie jedes Element dieser Liste und fügen es "wie es ist" in die neue Liste ein. Wenn Ihr nächster Artikel kurz nach dem aktuellen erstellt wurde, besteht eine gute Chance (keine Garantie!), Dass er daneben auf dem Heap gespeichert wird.
Nehmen wir an, dass Ihr Computer jedes Mal, wenn er ein Element in den Cache lädt, auch die
x
Elemente im nächsten Speicher lädt (Cache-Lokalität). Dann kann Ihr Computer das Referenzzählinkrement fürx+1
Elemente im selben Cache durchführen!Bei der gemischten Sequenz werden weiterhin die Elemente im nächsten Speicher geladen, diese sind jedoch nicht die Elemente im nächsten in der Liste. Es kann also das Referenzzählinkrement nicht ausführen, ohne "wirklich" nach dem nächsten Element zu suchen.
TL; DR: Die tatsächliche Geschwindigkeit hängt davon ab, was vor dem Kopieren passiert ist: In welcher Reihenfolge wurden diese Elemente erstellt und in welcher Reihenfolge befinden sich diese in der Liste.
Sie können dies überprüfen, indem Sie sich Folgendes ansehen
id
:Nur um einen kurzen Auszug zu zeigen:
Diese Objekte sind also wirklich "nebeneinander auf dem Haufen". Mit sind
shuffle
sie nicht:Was zeigt, dass diese im Gedächtnis nicht wirklich nebeneinander liegen:
Wichtige Notiz:
Ich habe mir das nicht ausgedacht. Die meisten Informationen finden Sie im Blogpost von Ricky Stewart .
Diese Antwort basiert auf der "offiziellen" CPython-Implementierung von Python. Die Details in anderen Implementierungen (Jython, PyPy, IronPython, ...) können abweichen. Vielen Dank an JörgWMittag für diesen Hinweis .
quelle
list_slice
und in Zeile 453 sehen Sie denPy_INCREF(v);
Aufruf, der für den Zugriff auf das Heap-zugewiesene Objekt erforderlich ist.a = [0] * 10**7
(von 10 ** 6, weil das zu instabil war), die sogar schneller ist als die Verwendunga = range(10**7)
(um einen Faktor von etwa 1,25). Klar, denn das ist noch besser für das Caching.[0,1,2,3]*((10**6) // 4)
ist so schnell wiea = [0] * 10**6
. Bei Ganzzahlen von 0 bis 255 kommt jedoch noch eine weitere Tatsache hinzu: Diese sind interniert, sodass bei diesen die Reihenfolge der Erstellung (in Ihrem Skript) nicht mehr wichtig ist - da sie beim Starten von Python erstellt werden.Wenn Sie die Listenelemente mischen, haben sie eine schlechtere Referenzlokalität, was zu einer schlechteren Cache-Leistung führt.
Sie könnten denken, dass beim Kopieren der Liste nur die Referenzen und nicht die Objekte kopiert werden, sodass ihre Position auf dem Heap keine Rolle spielen sollte. Beim Kopieren muss jedoch weiterhin auf jedes Objekt zugegriffen werden, um die Nachzählung zu ändern.
quelle
Wie bereits von anderen erklärt, es ist nicht nur die Referenzen zu kopieren , sondern erhöht auch die Referenzzahl innerhalb der Objekte und damit die Objekte werden abgerufen und der Cache eine Rolle spielt.
Hier möchte ich nur weitere Experimente hinzufügen. Nicht so sehr über gemischt oder nicht gemischt (wo beim Zugriff auf ein Element der Cache möglicherweise übersehen wird, aber die folgenden Elemente in den Cache gelangen, damit sie getroffen werden). Aber über das Wiederholen von Elementen, bei denen spätere Zugriffe auf dasselbe Element den Cache treffen könnten, weil sich das Element noch im Cache befindet.
Testen eines normalen Bereichs:
Eine Liste derselben Größe, bei der jedoch nur ein Element immer wieder wiederholt wird, ist schneller, da sie ständig in den Cache gelangt:
Und es scheint egal zu sein, um welche Zahl es sich handelt:
Interessanterweise wird es noch schneller, wenn ich stattdessen dieselben zwei oder vier Elemente wiederhole:
Ich denke, etwas mag es nicht, wenn derselbe einzelne Zähler ständig erhöht wird. Vielleicht ist eine Pipeline stehen geblieben, weil jede Erhöhung auf das Ergebnis der vorherigen Erhöhung warten muss, aber dies ist eine wilde Vermutung.
Wie auch immer, versuchen Sie dies für eine noch größere Anzahl von wiederholten Elementen:
Die Ausgabe (erste Spalte ist die Anzahl der verschiedenen Elemente, für die ich dreimal teste und dann den Durchschnitt nehme):
Von ungefähr 2,8 Sekunden für ein einzelnes (wiederholtes) Element fällt es für 2, 4, 8, 16, ... verschiedene Elemente auf ungefähr 2,2 Sekunden ab und bleibt bei ungefähr 2,2 Sekunden bis zu den Hunderttausenden. Ich denke, dies verwendet meinen L2-Cache (4 × 256 KB, ich habe einen i7-6700 ).
Dann steigen die Zeiten in wenigen Schritten auf 3,5 Sekunden. Ich denke, dies verwendet eine Mischung aus meinem L2-Cache und meinem L3-Cache (8 MB), bis dies ebenfalls "erschöpft" ist.
Am Ende bleibt es bei ungefähr 3,5 Sekunden, denke ich, weil meine Caches bei den wiederholten Elementen nicht mehr helfen.
quelle
Vor dem Mischen sind die benachbarten Indexobjekte, wenn sie im Heap zugeordnet sind, im Speicher benachbart, und die Speicher-Trefferquote ist beim Zugriff hoch. Nach dem Mischen befindet sich das Objekt des benachbarten Index der neuen Liste nicht im Speicher. Angrenzend ist die Trefferquote sehr schlecht.
quelle