Welche semantischen Merkmale von Python (und anderen dynamischen Sprachen) tragen zu seiner Langsamkeit bei?

26

Ich kenne Python nicht sehr gut. Ich versuche genauer zu verstehen, welche genauen Merkmale dynamischer Sprachen (à la Python, Lua, Scheme, Perl, Ruby, ...) ihre Implementierungen langsam machen.

In diesem Fall würde eine mit Lua 5.3 umsetzbare Maschine Lua intuitiv sehr langsam machen, aber in der Praxis wird gemunkelt, dass Lua ziemlich schnell (und schneller als Python) ist.

Ich habe auch die Intuition (vielleicht eine falsche), dass, da der Speicher auf aktuellen Prozessoren viel langsamer ist als die Rohberechnung (ein Speicherzugriff mit einem Cache-Fehler benötigt die gleiche Zeit wie Hunderte von arithmetischen Operationen), die dynamische Typprüfung (à la if (value->type != INTEGER_TAG) return;in) C parlance) könnte ziemlich schnell laufen.

Natürlich kann eine vollständige Programmanalyse (wie dies bei der Implementierung des Stalin-Schemas der Fall ist) eine dynamische Sprachimplementierung bewirken, da ein Übersetzer schnell ausgeführt wird. Nehmen wir jedoch an, ich hätte zunächst keine Zeit, einen vollständigen Programmanalysator zu entwerfen.

(Ich entwerfe eine Art dynamische Sprache in meinem MELT-Monitor , und ein Teil davon würde in C übersetzt.)

Basile Starynkevitch
quelle
1
Lua-Leistungstipps , in denen erklärt wird, warum einige Lua-Programme langsam sind und wie sie behoben werden können.
Robert Harvey

Antworten:

24

Welche semantischen Merkmale von Python (und anderen dynamischen Sprachen) tragen zu seiner Langsamkeit bei?

Keiner.

Die Leistung von Sprachimplementierungen hängt von Geld, Ressourcen und Doktorarbeiten ab, nicht von Sprachfunktionen. Self ist viel dynamischer als Smalltalk und etwas dynamischer als Python, Ruby, ECMAScript oder Lua, und es gab eine VM, die alle vorhandenen Lisp- und Smalltalk-VMs übertraf (tatsächlich wurde die Self-Distribution mit einem kleinen in Self geschriebenen Smalltalk-Interpreter ausgeliefert) , und selbst das war schneller als die meisten existierenden Smalltalk-VMs) und war konkurrenzfähig mit und manchmal sogar schneller als die damaligen C ++ - Implementierungen.

Dann hörte Sun auf, Self zu finanzieren, und IBM, Microsoft, Intel und Co. begannen, C ++ zu finanzieren, und der Trend kehrte sich um. Die Self-Entwickler verließen Sun, um ein eigenes Unternehmen zu gründen, in dem sie die für die Self-VM entwickelte Technologie verwendeten, um eine der schnellsten Smalltalk-VMs aller Zeiten (die animorphe VM) zu erstellen. Anschließend kaufte Sun das Unternehmen und eine leicht modifizierte Version von zurück Diese Smalltalk-VM ist jetzt besser unter dem Namen "HotSpot JVM" bekannt. Ironischerweise sehen Java-Programmierer dynamische Sprachen als "langsam" an, tatsächlich als Javawar langsam, bis es dynamische Sprachtechnologie annahm. (Ja, das ist richtig: Die HotSpot-JVM ist im Wesentlichen eine Smalltalk-VM. Der Bytecode-Prüfer führt viele Typprüfungen durch, aber sobald der Bytecode vom Prüfer akzeptiert wird, tun dies die VM und insbesondere der Optimierer und der JIT nicht mehr viel interesse bei den statischen typen!)

CPython erledigt einfach nicht viele Dinge, die dynamische Sprachen (oder eher den dynamischen Versand) schnell machen: dynamische Kompilierung (JIT), dynamische Optimierung, spekulatives Inlining, adaptive Optimierung, dynamische Deoptimierung, dynamisches Typ-Feedback / Inferenz. Es gibt auch das Problem, dass fast die gesamte Kern- und Standardbibliothek in C geschrieben ist, was bedeutet, dass Python, selbst wenn Sie es plötzlich 100x schneller machen, nicht viel hilft, weil so etwas wie 95% des Codes von a ausgeführt wird Python-Programm ist C, nicht Python. Wenn alles in Python geschrieben wäre, würden selbst moderate Beschleunigungen einen Lawineneffekt hervorrufen, bei dem die Algorithmen schneller und die Kerndatenstrukturen schneller werden, aber natürlich werden die Kerndatenstrukturen auch in den Algorithmen und den Kernalgorithmen und Kerndaten verwendet Strukturen werden überall sonst verwendet,

Es gibt einige Dinge, die für speicherverwaltete OO-Sprachen (dynamisch oder nicht) in heutigen Systemen notorisch schlecht sind. Virtueller Speicher und Speicherschutz können die Leistung der Speicherbereinigung und die Systemleistung im Allgemeinen beeinträchtigen. Und in einer speichersicheren Sprache ist das völlig unnötig: Warum vor illegalen Speicherzugriffen schützen, wenn es in der Sprache zunächst keine Speicherzugriffe gibt? Azul hat heraus modernen leistungsfähigen MMU (Intel Nehalem und neuere und äquivalente AMD) zu verwenden , um Hilfe Garbage Collection , anstatt es zu behindern, sondern auch wenn es von der CPU unterstützt wird, sind die aktuellen Speichersubsysteme von Mainstream - O nicht stark genug um dies zu ermöglichen (die eigentlich warum Azul ist JVM auf das blanke Metall virtualisiert läuft neben das Betriebssystem, nicht in ihm).

Im Projekt Singularity OS hat Microsoft eine Auswirkung von ~ 30% auf die Systemleistung gemessen, wenn der MMU-Schutz anstelle des Typsystems für die Prozesstrennung verwendet wurde.

Eine andere Sache, die Azul beim Aufbau seiner spezialisierten Java-CPUs bemerkte, war, dass moderne Mainstream-CPUs sich auf das völlig Falsche konzentrieren, wenn sie versuchen, die Kosten von Cache-Fehlern zu senken: Sie versuchen, die Anzahl von Cache-Fehlern durch Dinge wie Verzweigungsvorhersage, Speicher-Prefetching, und so weiter. In einem stark polymorphen OO-Programm sind die Zugriffsmuster jedoch grundsätzlich pseudozufällig, es ist einfach nichts vorherzusagen. Alle diese Transistoren sind also einfach verschwendet, und stattdessen sollten die Kosten für jeden einzelnen Cache-Fehler gesenkt werden. (Die Gesamtkosten betragen #misses * cost, der Mainstream versucht, den ersten Fehler zu beheben, Azul den zweiten.) Die Java-Compute-Beschleuniger von Azul könnten im Flug 20000 gleichzeitige Cache-Fehler aufweisen und dennoch Fortschritte erzielen.

Wenn Azul begonnen, sie dachten , sie würden einige nehmen off-the-shelf - I / O - Komponenten und entwerfen ihre eigenen spezialisierten CPU - Kern, aber was sie eigentlich am Ende , um zu tun war das genaue Gegenteil: sie haben eine ziemlich Standard - off-the Regal 3-Adressen-RISC-Kern und entwarf ihren eigenen Speichercontroller, MMU und Cache-Subsystem.

tl; dr : Die "Langsamkeit" von Python ist keine Eigenschaft der Sprache, sondern a) ihre naive (primäre) Implementierung und b) die Tatsache, dass moderne CPUs und Betriebssysteme speziell dafür ausgelegt sind, C schnell laufen zu lassen, und die Funktionen, die sie bieten Habe für C entweder nicht geholfen (Cache) oder sogar die Python-Performance aktiv beeinträchtigt (virtueller Speicher).

Und Sie können hier so ziemlich jede speicherverwaltete Sprache mit dynamischem Ad-hoc-Polymorphismus einfügen. Wenn es um die Herausforderungen einer effizienten Implementierung geht, sind sogar Python und Java so ziemlich "dieselbe Sprache".

Jörg W. Mittag
quelle
Haben Sie einen Link oder eine Referenz für Azul?
Basile Starynkevitch
4
Ich stimme nicht zu, dass die Semantik einer Sprache keinen Einfluss auf ihre Fähigkeit hat, effizient implementiert zu werden. Ja, eine gute JIT-Implementierung mit erstklassigen Optimierungen und Analysen kann die Leistung einer Sprache erheblich verbessern, aber am Ende gibt es bestimmte Aspekte der Semantik, die unvermeidlich zu Engpässen führen werden. Unabhängig davon, ob C ein striktes Aliasing von Zeigern verlangt oder Pythons Anforderung, Listenoperationen atomar auszuführen, gibt es bestimmte semantische Entscheidungen, die die Leistung einiger Anwendungen unvermeidlich beeinträchtigen.
Jules
1
Nebenbei ... haben Sie eine Referenz für diese 30% ige Verbesserung für Singularity? Ich bin seit vielen Jahren ein Verfechter von sprachbasierten Schutz-Betriebssystemen, habe diese Zahl jedoch noch nie zuvor gesehen und finde sie ziemlich verblüffend (Zahlen, die ich in der Vergangenheit näher an 10% betrachtet habe) und frage mich, was Sie haben getan, um so viel Verbesserung
Jules
5
@ MasonWheeler: Weil es nur beschissene Python-Implementierungen gibt. Kein Python-Implementierer hat auch nur einen Bruchteil des Geldes, der Mitarbeiter, der Forschung und der Ressourcen ausgegeben, die IBM, Sun, Oracle, Google und Co. für J9, JRockit, HotSpot und Co. ausgegeben haben Haben Sie die Arbeitskräfte, die Oracle nur für den Müllsammler ausgibt. IBM ist auf einer Implementierung von Eclipse OMR (die componentized Open-Source - VM Rahmen extrahiert aus J9) basiert Python arbeiten, ich bin bereit zu wetten , dass seine Leistung in einer Größenordnung von J9 wird gut
Jörg W Mittag
2
Für den Datensatz ist C im Vergleich zu Fortran für numerische Arbeiten langsam, da Fortran striktes Aliasing erzwingt, sodass der Optimierer aggressiver sein kann.
Michael Shopsin
8

Während die aktuelle Implementierung von Python (der viele Optimierungen fehlen, die von anderen dynamischen Sprachen ausgeführt werden, z. B. moderne JavaScript-Implementierungen und, wie Sie bereits betont haben, Lua) die meisten Probleme verursacht, weist sie einige semantische Probleme auf, die dazu führen würden Es ist schwierig für eine Implementierung, mit anderen Sprachen zu konkurrieren, zumindest in bestimmten Bereichen. Einige, die besonders erwägenswert sind:

  • Listen- und Wörterbuchoperationen müssen in der Sprachdefinition atomar sein. Dies bedeutet, dass ein JIT-Compiler, wenn er nicht nachweisen kann, dass kein Verweis auf ein Listenobjekt seinem aktuellen Thread entgangen ist (eine Analyse, die in vielen Fällen schwierig und im allgemeinen Fall unmöglich ist), sicherstellen muss, dass der Zugriff auf das Objekt serialisiert wird (z. B. über Verriegelung). Die CPython-Implementierung behebt dieses Problem, indem sie die berüchtigte "globale Interpretersperre" verwendet, die verhindert, dass Python-Code in Multiprozessor-Umgebungen mit Multithread-Techniken effektiv verwendet wird, und obwohl andere Lösungen möglich sind, weisen alle Leistungsprobleme auf.

  • In Python gibt es keinen Mechanismus zum Angeben der Verwendung von Wertobjekten. Alles wird als Referenz behandelt, wobei zusätzliche Indirektion hinzugefügt wird, wenn dies nicht unbedingt erforderlich ist. Während es für einen JIT-Compiler in einigen Fällen möglich ist, Wertobjekte abzuleiten und diese automatisch zu optimieren, ist dies nicht generell möglich, und daher ist Code, der nicht sorgfältig geschrieben wurde, um sicherzustellen, dass eine Optimierung möglich ist (was etwas von einer schwarzen Kunst ist). wird leiden.

  • Python hat eine evalFunktion, was bedeutet, dass ein JIT-Compiler keine Annahmen über nicht auftretende Aktionen treffen kann, selbst wenn er eine Ganzprogrammanalyse durchführt, solange er evalnur einmal verwendet wird. Ein Python-Compiler kann beispielsweise nicht davon ausgehen, dass eine Klasse keine Unterklassen hat, und daher Methodenaufrufe devirtualisieren, da diese Annahme später über einen Aufruf von negiert werden könnte eval. Stattdessen müssen dynamische Typprüfungen durchgeführt werden, um sicherzustellen, dass Annahmen, die von systemeigenem kompiliertem Code getroffen wurden, vor der Ausführung dieses Codes nicht ungültig wurden.

Jules
quelle
3
Der letztere Punkt kann durch evalAuslösen einer Neukompilierung und / oder einer Deoptimierung gemildert werden .
Jörg W Mittag
4
Das gibt es übrigens auch nicht nur in Python. Java (oder besser gesagt die JVM) verfügt über dynamisches Laden von Code und dynamisches Verknüpfen, sodass die Analyse der Klassenhierarchie auch dort der Lösung des Halteproblems entspricht. HotSpot integriert jedoch fröhlich spekulativ polymorphe Methoden, und wenn sich etwas in der Klassenhierarchie ändert, werden sie einfach wieder deaktiviert.
Jörg W Mittag