Hinweis: Ich bin ein Ruby-Entwickler, der versucht, meinen Weg in Python zu finden.
Als ich herausfinden wollte, warum einige Skripte mylist[:]
anstelle von list(mylist)
verwenden, um Listen zu duplizieren, habe ich einen schnellen Vergleich der verschiedenen Methoden gemacht, um range(10)
zu duplizieren (siehe Code unten).
BEARBEITEN: Ich habe die Tests aktualisiert, um Pythons timeit
wie unten vorgeschlagen zu verwenden. Dies macht es unmöglich, es direkt mit Ruby zu vergleichen, weil timeit nicht für die Schleifenbildung verantwortlich ist, während Rubys Benchmark
dies tut. Ruby-Code ist also nur für Referenz .
Python 2.7.2
%Vor%Als Referenz habe ich das gleiche Skript auch in Ruby geschrieben:
Ruby 1.9.2p0
%Vor% Frage 1: Was macht mylist[:]
anders? Es ist 25% schneller als gerade mylist[0:len(mylist)]
. Kopiert es direkt in den Speicher oder was?
Frage 2: edit: aktualisierte Benchmarks zeigen keine großen Unterschiede mehr in Python und Ruby. war: Habe ich die Tests auf eine offensichtlich ineffiziente Weise implementiert, so dass Ruby-Code so viel schneller ist als Python?
Jetzt die Codelisten:
Python:
%Vor%Ruby:
%Vor% Verwenden Sie das timeit
-Modul in Python zum Testen von Timings.
Ergebnisse:
%Vor% Es ist sicherlich die zusätzlichen Schritte in a[0:len(a)]
sind der Grund für die Langsamkeit.
Hier ist der Byte-Code-Vergleich der beiden:
%Vor% Ich kann das Ruby-Timing und das Python-Timing nicht kommentieren. Aber ich kann Kommentar zu list
vs. slice
abgeben. Hier ist eine kurze Überprüfung des Bytecodes:
Beachten Sie, dass list
ein LOAD_GLOBAL
benötigt, um die Funktion list
zu finden. Das Nachschlagen von Globals (und das Aufrufen von Funktionen) in Python ist relativ langsam. Dies würde erklären, warum a[0:len(a)]
auch langsamer ist. Denken Sie auch daran, dass list
in der Lage sein muss, beliebige Iteratoren zu verarbeiten, während dies beim Slicing nicht möglich ist. Dies bedeutet, dass list
eine neue Liste zuweisen und Elemente in diese Liste packen muss, wenn sie über die Liste iteriert und bei Bedarf die Größe ändert. Es gibt ein paar Dinge, die teuer sind: Größenanpassung wenn nötig und Iteration (effektiv in Python, nicht in C). Mit der Slicing-Methode können Sie die Speichergröße berechnen, die Sie benötigen, so dass Sie wahrscheinlich die Größenänderung vermeiden können. Die Iteration kann vollständig in C erfolgen (wahrscheinlich mit memcpy
oder etwas.
Haftungsausschluss : Ich bin kein Python-Entwickler, also weiß ich nicht, wie die Interna von list()
sicher implementiert sind. Ich spekuliere nur basierend, was ich von der Spezifikation weiß.
BEARBEITEN - Also habe ich mir die Quelle angeschaut (mit einer kleinen Anleitung von Martijn). Der entsprechende Code befindet sich in listobject.c
. list
ruft list_init
auf, das dann listextend
in Zeile 799 aufruft. Diese Funktion hat einige Überprüfungen, um zu sehen, ob sie eine schnelle Verzweigung verwenden kann, wenn das Objekt eine Liste oder ein Tupel ist (Zeile 812). Abschließend wird das schwere Heben ab Linie 834 begonnen:
Vergleichen Sie das mit der Slice-Version, die in list_subscript
(Zeile 2544) definiert ist. Das ruft list_slice
(Zeile 2570) auf, wo das schwere Heben von der folgenden Schleife (Zeile 486) ausgeführt wird:
Sie sind praktisch derselbe Code, daher ist es nicht verwunderlich, dass die Performance für große Listen fast gleich ist (wo der Overhead der kleinen Dinge wie das Entpacken von Slices, Nachschlagen globaler Variablen usw. weniger wichtig ist)
So würde ich die Python-Tests (und die Ergebnisse für mein Ubuntu-System) ausführen:
%Vor%