Ich habe kürzlich die Verarbeitungsgeschwindigkeiten von []
und verglichen list()
und war überrascht zu entdecken, dass diese mehr als dreimal schneller[]
laufen als . Ich habe den gleichen Test mit und durchgeführt und die Ergebnisse waren praktisch identisch: und beide dauerten ungefähr 0,128 Sekunden / Million Zyklen, während und jeweils ungefähr 0,428 Sekunden / Million Zyklen dauerten.list()
{}
dict()
[]
{}
list()
dict()
Warum ist das? Haben []
und {}
(und wahrscheinlich ()
und ''
auch) sofort eine Kopie von einigen leeren Lagern wörtlichen passieren zurück , während ihre explizit genannten Pendants ( list()
, dict()
, tuple()
, str()
) voll gehen über ein Objekt zu schaffen, ob sie tatsächlich Elemente haben?
Ich habe keine Ahnung, wie sich diese beiden Methoden unterscheiden, aber ich würde es gerne herausfinden. Ich konnte keine Antwort in den Dokumenten oder auf SO finden, und die Suche nach leeren Klammern erwies sich als problematischer als erwartet.
Ich habe mein Timing Ergebnis durch den Aufruf timeit.timeit("[]")
und timeit.timeit("list()")
, und , timeit.timeit("{}")
und timeit.timeit("dict()")
, Listen und Wörterbücher zu vergleichen ist. Ich verwende Python 2.7.9.
Ich habe vor kurzem entdeckt „ Warum ist wenn Wahr langsamer als wenn 1? “, Das die Leistung vergleicht if True
zu if 1
und scheint auf einem ähnlichen wörtliche-versus-globales Szenario zu berühren; Vielleicht lohnt es sich auch darüber nachzudenken.
quelle
()
und''
sind etwas Besonderes, da sie nicht nur leer, sondern auch unveränderlich sind. Daher ist es ein einfacher Gewinn, sie zu Singletons zu machen. Sie konstruieren nicht einmal neue Objekte, sondern laden nur den Singleton für das leeretuple
/str
. Technisch gesehen ein Implementierungsdetail, aber ich kann mir nur schwer vorstellen, warum sie das leere / aus Leistungsgründen nicht zwischenspeichern würden . Ihre Intuition über und die Rückgabe eines Aktienliteral war also falsch, aber es gilt für und .tuple
str
[]
{}
()
''
{}
schneller als anrufenset()
?Antworten:
Weil
[]
und{}
sind wörtliche Syntax . Python kann Bytecode erstellen, um nur die Listen- oder Wörterbuchobjekte zu erstellen:list()
unddict()
sind separate Objekte. Ihre Namen müssen aufgelöst werden, der Stapel muss beteiligt sein, um die Argumente zu pushen, der Frame muss gespeichert werden, um ihn später abzurufen, und es muss ein Aufruf erfolgen. Das alles braucht mehr Zeit.Für den leeren Fall bedeutet dies, dass Sie mindestens ein
LOAD_NAME
(das den globalen Namespace sowie das__builtin__
Modul durchsuchen muss ) gefolgt von einem habenCALL_FUNCTION
, das den aktuellen Frame beibehalten muss:Sie können die Namenssuche separat zeitlich festlegen mit
timeit
:Die Zeitdiskrepanz dort ist wahrscheinlich eine Wörterbuch-Hash-Kollision. Subtrahieren Sie diese Zeiten von den Zeiten für den Aufruf dieser Objekte und vergleichen Sie das Ergebnis mit den Zeiten für die Verwendung von Literalen:
Das Aufrufen des Objekts dauert also zusätzliche
1.00 - 0.31 - 0.30 == 0.39
Sekunden pro 10 Millionen Anrufe.Sie können die globalen Suchkosten vermeiden, indem Sie die globalen Namen als
timeit
lokale Namen aliasen (mithilfe eines Setups ist alles, was Sie an einen Namen binden, lokal):Aber diese
CALL_FUNCTION
Kosten können Sie nie überwinden .quelle
list()
erfordert eine globale Suche und einen Funktionsaufruf, wird jedoch[]
zu einer einzelnen Anweisung kompiliert. Sehen:quelle
Denn
list
ist eine Funktion zum Konvertieren beispielsweise eines Strings in ein Listenobjekt, während[]
eine Liste von Anfang an erstellt wird. Versuchen Sie dies (könnte für Sie sinnvoller sein):Während
Gibt Ihnen eine aktuelle Liste mit allem, was Sie hineingelegt haben.
quelle
[]
ist schneller alslist()
, nicht warum['wham bam']
ist schneller alslist('wham bam')
.[]
/list()
genau das gleiche ist wie['wham']
/list('wham')
weil sie die gleichen variablen Unterschiede haben1000/10
wie100/1
in Mathe. Sie könnten theoretisch wegnehmenwham bam
und die Tatsache wäre immer noch die gleiche, dielist()
versucht, etwas durch Aufrufen eines Funktionsnamens zu konvertieren, während[]
direkt nur die Variable konvertiert wird. Funktionsaufrufe sind unterschiedlich ja, dies ist nur eine logische Übersicht über das Problem, da beispielsweise eine Netzwerkkarte eines Unternehmens auch eine Lösung / ein Problem logisch darstellt. Stimmen Sie ab, wie Sie möchten.Die Antworten hier sind großartig, auf den Punkt und decken diese Frage vollständig ab. Ich werde für Interessierte einen weiteren Schritt vom Byte-Code weggehen. Ich verwende das neueste Repo von CPython. ältere Versionen verhalten sich in dieser Hinsicht ähnlich, es können jedoch geringfügige Änderungen vorgenommen werden.
Hier ist eine Aufschlüsselung der Ausführung für jede dieser,
BUILD_LIST
für[]
undCALL_FUNCTION
fürlist()
.Die
BUILD_LIST
Anweisung:Sie sollten nur den Horror sehen:
Schrecklich verwickelt, ich weiß. So einfach ist das:
PyList_New
(dies ordnet hauptsächlich den Speicher für ein neues Listenobjekt zu),oparg
signalisieren Sie die Anzahl der Argumente auf dem Stapel. Direkt auf den Punkt.if (list==NULL)
.PyList_SET_ITEM
(einem Makro) hinzu.Kein Wunder, dass es schnell geht! Es ist maßgeschneidert für die Erstellung neuer Listen, sonst nichts :-)
Die
CALL_FUNCTION
Anweisung:Folgendes sehen Sie als Erstes, wenn Sie sich die Code-Behandlung ansehen
CALL_FUNCTION
:Sieht ziemlich harmlos aus, oder? Nun, nein, leider nicht,
call_function
ist kein einfacher Typ, der die Funktion sofort aufruft, es kann nicht. Stattdessen wird das Objekt vom Stapel abgerufen, alle Argumente des Stapels erfasst und dann basierend auf dem Objekttyp gewechselt. ist es ein:PyCFunction_Type
? Nein, es istlist
,list
ist nicht vom TypPyCFunction
PyMethodType
? Nein, siehe vorher.PyFunctionType
? Nein, siehe vorher.Wir nennen den
list
Typ, das übergebene Argumentcall_function
istPyList_Type
. CPython muss jetzt eine generische Funktion aufrufen, um alle aufrufbaren Objekte mit dem Namen_PyObject_FastCallKeywords
yay mehr Funktionsaufrufe zu verarbeiten.Diese Funktion überprüft erneut einige Funktionstypen (was ich nicht verstehen kann, warum) und ruft dann nach dem Erstellen eines Diktats für kwargs bei Bedarf auf
_PyObject_FastCallDict
._PyObject_FastCallDict
bringt uns endlich irgendwohin! Nach der Durchführung noch mehr Kontrollen es packt dentp_call
Schlitz aus demtype
von dertype
wir in bestanden haben, das heißt, es packttype.tp_call
. Anschließend wird aus den übergebenen Argumenten ein Tupel erstellt,_PyStack_AsTuple
und schließlich kann ein Aufruf erfolgen !tp_call
, der übereinstimmt,type.__call__
übernimmt und erstellt schließlich das Listenobjekt. Es ruft die Listen auf__new__
, die dem entspricht ,PyType_GenericNew
mit für sie und weist SpeicherPyType_GenericAlloc
: Dies ist eigentlich der Teil ist , wo es mit aufholtPyList_New
schließlich . Alle vorherigen sind erforderlich, um Objekte generisch zu behandeln.Am Ende
type_call
ruftlist.__init__
die Liste auf und initialisiert sie mit allen verfügbaren Argumenten. Dann kehren wir auf dem Weg zurück, den wir gekommen sind. :-)Denken Sie zum Schluss daran
LOAD_NAME
, dass dies ein weiterer Typ ist, der hier einen Beitrag leistet.Es ist leicht zu erkennen, dass Python im Umgang mit unseren Eingaben im Allgemeinen durch Reifen springen muss, um tatsächlich die geeignete
C
Funktion für die Arbeit herauszufinden . Es hat nicht die Knappheit, es sofort anzurufen, weil es dynamisch ist, jemand maskieren könntelist
( und Junge tun viele Leute ) und ein anderer Weg eingeschlagen werden muss.Hier
list()
verliert viel: Das erforschende Python muss tun, um herauszufinden, was zum Teufel es tun soll.Wörtliche Syntax bedeutet andererseits genau eines; es kann nicht geändert werden und verhält sich immer auf eine vorher festgelegte Weise.
Fußnote: Alle Funktionsnamen können von einer Version zur anderen geändert werden. Der Punkt bleibt bestehen und wird höchstwahrscheinlich in zukünftigen Versionen bestehen. Es ist die dynamische Suche, die die Dinge verlangsamt.
quelle
Der Hauptgrund dafür ist, dass Python
list()
genau wie eine benutzerdefinierte Funktion behandelt wird. Dies bedeutet, dass Sie sie abfangen können, indem Sie etwas anderes aliasen und etwas andereslist
tun (z. B. Ihre eigene Unterklassenliste oder möglicherweise eine Deque verwenden).Es wird sofort eine neue Instanz einer eingebauten Liste mit erstellt
[]
.Meine Erklärung versucht, Ihnen die Intuition dafür zu geben.
Erläuterung
[]
ist allgemein als wörtliche Syntax bekannt.In der Grammatik wird dies als "Listenanzeige" bezeichnet. Aus den Dokumenten :
Kurz gesagt bedeutet dies, dass ein eingebautes Objekt vom Typ ist
list
erstellt wird.Dies lässt sich nicht umgehen - was bedeutet, dass Python dies so schnell wie möglich tun kann.
Auf der anderen Seite
list()
kann das Erstellen eines integrierten Systemslist
mithilfe des integrierten Listenkonstruktors abgefangen werden .Angenommen, wir möchten, dass unsere Listen geräuschvoll erstellt werden:
Wir könnten dann den Namen
list
auf globaler Ebene auf Modulebene abfangen , und wenn wir dann eine erstellenlist
, erstellen wir tatsächlich unsere Liste mit Untertypen:Ebenso könnten wir es aus dem globalen Namespace entfernen
und setzen Sie es in den eingebauten Namespace:
Und nun:
Beachten Sie, dass die Listenanzeige bedingungslos eine Liste erstellt:
Wir machen das wahrscheinlich nur vorübergehend, also lassen Sie uns unsere Änderungen rückgängig machen - entfernen Sie zuerst das neue
List
Objekt aus den eingebauten:Oh nein, wir haben den Überblick über das Original verloren.
Keine Sorge, wir können immer noch bekommen
list
- es ist die Art eines Listenliteral:Damit...
Wie wir gesehen haben - wir können überschreiben
list
-, aber wir können die Erstellung des Literaltyps nicht abfangen. Wenn wir verwenden, müssenlist
wir die Suche durchführen, um zu sehen, ob etwas da ist.Dann müssen wir anrufen, was auch immer wir nachgeschlagen haben. Aus der Grammatik:
Wir können sehen, dass es für jeden Namen dasselbe tut, nicht nur für die Liste:
Denn
[]
auf Python-Bytecode-Ebene gibt es keinen Funktionsaufruf:Es geht einfach direkt zum Erstellen der Liste ohne Nachschlagen oder Aufrufe auf Bytecode-Ebene.
Fazit
Wir haben gezeigt, dass
list
mit Benutzercode unter Verwendung der Scoping-Regeln abgefangen werden kann, und daslist()
nach einem aufrufbaren gesucht und dieser dann aufgerufen wird.Während
[]
ist eine Listenanzeige oder ein Literal, und vermeidet so die Namenssuche und Funktionsaufruf.quelle
list
und der Python-Compiler nicht sicher sein kann, ob er wirklich eine leere Liste zurückgibt.