Ich bin ein langjähriger Python-Benutzer. Vor ein paar Jahren habe ich angefangen, C ++ zu lernen, um zu sehen, was es in Bezug auf Geschwindigkeit bieten kann. Während dieser Zeit würde ich Python weiterhin als Werkzeug für das Prototyping verwenden. Dies schien ein gutes System zu sein: agile Entwicklung mit Python, schnelle Ausführung in C ++.
In letzter Zeit habe ich Python immer mehr verwendet und gelernt, wie man all die Fallstricke und Anti-Patterns vermeidet, die ich in meinen früheren Jahren mit der Sprache schnell verwendet habe. Nach meinem Verständnis kann die Verwendung bestimmter Funktionen (Listenverständnis, Aufzählungen usw.) die Leistung steigern.
Aber gibt es technische Einschränkungen oder Sprachfunktionen, die verhindern, dass mein Python-Skript so schnell ist wie ein gleichwertiges C ++ - Programm?
quelle
Antworten:
Ich habe diese Wand selbst getroffen, als ich vor ein paar Jahren einen Vollzeit-Python-Programmierjob annahm. Ich liebe Python, das tue ich wirklich, aber als ich anfing, die Leistung zu optimieren, hatte ich einige unhöfliche Schocks.
Die strengen Pythonisten können mich korrigieren, aber hier sind die Dinge, die ich gefunden habe, gemalt in sehr breiten Strichen.
Dies wirkt sich auf die Leistung aus, da zur Laufzeit zusätzliche Indirektionsebenen vorhanden sind und im Vergleich zu anderen Sprachen nicht viel Speicherplatz benötigt wird.
Andere können mit dem Ausführungsmodell sprechen, aber Python wird zur Laufzeit kompiliert und dann interpretiert, was bedeutet, dass es nicht bis zum Maschinencode reicht. Das wirkt sich auch auf die Leistung aus. Sie können problemlos C- oder C ++ - Module verknüpfen oder finden, aber wenn Sie Python direkt ausführen, wird dies einen Leistungseinbruch bedeuten.
In Webdienst-Benchmarks ist Python jetzt im Vergleich zu anderen zur Laufzeit kompilierbaren Sprachen wie Ruby oder PHP günstig. Aber es ist ziemlich weit hinter den meisten kompilierten Sprachen zurück. Sogar die Sprachen, die zu einer Zwischensprache kompiliert und in einer VM ausgeführt werden (wie Java oder C #), sind viel, viel besser.
Hier ist eine wirklich interessante Reihe von Benchmark-Tests, auf die ich gelegentlich Bezug nehme:
http://www.techempower.com/benchmarks/
(Trotzdem liebe ich Python immer noch sehr und wenn ich die Möglichkeit habe, die Sprache zu wählen, in der ich arbeite, ist dies meine erste Wahl. Meistens bin ich sowieso nicht an verrückte Durchsatzanforderungen gebunden.)
quelle
__slots__
. PyPy sollte in dieser Hinsicht viel besser abschneiden, aber ich weiß nicht genug, um es beurteilen zu können.Die Python-Referenzimplementierung ist der CPython-Interpreter. Es versucht relativ schnell zu sein, verwendet jedoch derzeit keine erweiterten Optimierungen. Und für viele Verwendungsszenarien ist dies eine gute Sache: Die Kompilierung zu einem Zwischencode erfolgt unmittelbar vor der Laufzeit, und jedes Mal, wenn das Programm ausgeführt wird, wird der Code neu kompiliert. Die für die Optimierung benötigte Zeit muss also gegen die durch Optimierungen gewonnene Zeit abgewogen werden. Wenn kein Nettogewinn erzielt wird, ist die Optimierung wertlos. Für ein sehr lang laufendes Programm oder ein Programm mit sehr engen Schleifen wäre die Verwendung erweiterter Optimierungen nützlich. CPython wird jedoch für einige Jobs verwendet, die eine aggressive Optimierung ausschließen:
Kurz laufende Skripte, die zB für Sysadmin-Aufgaben verwendet werden. Viele Betriebssysteme wie Ubuntu bauen einen Großteil ihrer Infrastruktur auf Python auf: CPython ist schnell genug für den Job, hat jedoch praktisch keine Startzeit. Solange es schneller als Bash ist, ist es gut.
CPython muss eine klare Semantik haben, da es sich um eine Referenzimplementierung handelt. Dies ermöglicht einfache Optimierungen wie "Optimieren der Implementierung des foo-Operators" oder "Kompilieren von Listenverständnissen zu schnellerem Bytecode", schließt jedoch im Allgemeinen Optimierungen aus, die Informationen zerstören, wie z. B. Inlining-Funktionen.
Natürlich gibt es mehr Python-Implementierungen als nur CPython:
Jython basiert auf der JVM. Die JVM kann den bereitgestellten Bytecode interpretieren oder JIT-kompilieren und verfügt über profilgesteuerte Optimierungen. Es leidet unter einer hohen Startzeit und es dauert eine Weile, bis die JIT einsetzt.
PyPy ist ein Stand der Technik, JITting Python VM. PyPy ist in RPython geschrieben, einer eingeschränkten Teilmenge von Python. Diese Teilmenge entfernt etwas Ausdruckskraft aus Python, ermöglicht jedoch die statische Ableitung des Typs einer beliebigen Variablen. Die in RPython geschriebene VM kann dann in C transpiliert werden, was eine RPython C-ähnliche Leistung ergibt. RPython ist jedoch immer noch ausdrucksstärker als C, was eine schnellere Entwicklung neuer Optimierungen ermöglicht. PyPy ist ein Beispiel für Compiler-Bootstrapping. PyPy (nicht RPython!) Ist größtenteils mit der CPython-Referenzimplementierung kompatibel.
Cython ist (wie RPython) ein inkompatibler Python-Dialekt mit statischer Typisierung. Es wird auch in C-Code transpiliert und kann problemlos C-Erweiterungen für den CPython-Interpreter generieren.
Wenn Sie bereit sind, Ihren Python-Code in Cython oder RPython zu übersetzen, erhalten Sie eine C-ähnliche Leistung. Sie sollten jedoch nicht als "Teilmenge von Python" verstanden werden, sondern als "C mit pythonischer Syntax". Wenn Sie zu PyPy wechseln, wird Ihr Vanille-Python-Code erheblich beschleunigt, kann aber auch nicht mit in C oder C ++ geschriebenen Erweiterungen verbunden werden.
Aber welche Eigenschaften oder Merkmale verhindern, dass Vanilla Python abgesehen von langen Startzeiten ein C-ähnliches Leistungsniveau erreicht?
Mitwirkende und Finanzierung. Im Gegensatz zu Java oder C # gibt es keine einzige Fahrgesellschaft hinter der Sprache, die daran interessiert ist, diese Sprache zur besten ihrer Klasse zu machen. Dies beschränkt die Entwicklung hauptsächlich auf Freiwillige und gelegentliche Zuschüsse.
Späte Bindung und das Fehlen jeglicher statischer Typisierung. Python erlaubt es uns, Mist wie folgt zu schreiben:
In Python kann jede Variable jederzeit neu zugewiesen werden. Dies verhindert das Zwischenspeichern oder Inlining. Jeder Zugriff muss über die Variable erfolgen. Diese Indirektion belastet die Leistung. Natürlich: Wenn Ihr Code solche verrückten Dinge nicht tut, so dass jeder Variablen vor dem Kompilieren ein definitiver Typ zugewiesen werden kann und jede Variable nur einmal zugewiesen wird, könnte theoretisch ein effizienteres Ausführungsmodell ausgewählt werden. Eine Sprache in diesem Sinne würde eine Möglichkeit bieten, Bezeichner als Konstanten zu markieren und zumindest optionale Typanmerkungen zuzulassen („schrittweise Eingabe“).
Ein fragwürdiges Objektmodell. Wenn keine Slots verwendet werden, ist es schwierig herauszufinden, welche Felder ein Objekt hat (ein Python-Objekt ist im Wesentlichen eine Hash-Tabelle von Feldern). Und selbst wenn wir dort sind, haben wir noch keine Ahnung, welche Typen diese Felder haben. Dies verhindert, dass Objekte als dicht gepackte Strukturen dargestellt werden, wie dies in C ++ der Fall ist. (Natürlich ist die Darstellung von Objekten in C ++ auch nicht ideal: Aufgrund der strukturellen Natur gehören sogar private Felder zur öffentlichen Schnittstelle eines Objekts.)
Müllabfuhr. In vielen Fällen konnte die GC vollständig vermieden werden. Mit C ++ können wir Objekte statisch zuordnen, die automatisch zerstört werden, wenn der aktuelle Bereich verlassen wird :
Type instance(args);
. Bis dahin lebt das Objekt und kann an andere Funktionen verliehen werden. Dies erfolgt normalerweise über „Pass-by-Reference“. Mit Sprachen wie Rust kann der Compiler statisch überprüfen, ob kein Zeiger auf ein solches Objekt die Lebensdauer des Objekts überschreitet. Dieses Speicherverwaltungsschema ist vollständig vorhersehbar, hocheffizient und eignet sich für die meisten Fälle ohne komplizierte Objektgraphen. Leider wurde Python nicht für die Speicherverwaltung entwickelt. Theoretisch kann die Fluchtanalyse verwendet werden, um Fälle zu finden, in denen GC vermieden werden kann. In der Praxis können einfache Methodenketten wiefoo().bar().baz()
muss eine große Anzahl kurzlebiger Objekte auf dem Heap zuordnen (Generations-GC ist eine Möglichkeit, dieses Problem klein zu halten).In anderen Fällen kennt der Programmierer möglicherweise bereits die endgültige Größe eines Objekts, z. B. einer Liste. Leider bietet Python keine Möglichkeit, dies beim Erstellen einer neuen Liste zu kommunizieren. Stattdessen werden neue Elemente an das Ende verschoben, was möglicherweise mehrere Neuzuweisungen erfordert. Ein paar Anmerkungen:
Listen einer bestimmten Größe können wie erstellt werden
fixed_size = [None] * size
. Der Speicher für die Objekte in dieser Liste muss jedoch separat zugewiesen werden. Kontrast C ++, wo wir tun könnenstd::array<Type, size> fixed_size
.Gepackte Arrays eines bestimmten nativen Typs können in Python über das
array
integrierte Modul erstellt werden. Bietet außerdemnumpy
eine effiziente Darstellung von Datenpuffern mit bestimmten Formen für native numerische Typen.Zusammenfassung
Python wurde für eine einfache Bedienung entwickelt, nicht für die Leistung. Das Design erschwert die Erstellung einer hocheffizienten Implementierung. Wenn der Programmierer auf problematische Funktionen verzichtet, kann ein Compiler, der die verbleibenden Redewendungen versteht, effizienten Code ausgeben, der in seiner Leistung mit C mithalten kann.
quelle
Ja. Das Hauptproblem besteht darin, dass die Sprache als dynamisch definiert ist - das heißt, Sie wissen nie, was Sie tun, bis Sie es tun. Das macht es sehr schwer , eine effiziente Maschinencode zu erzeugen, weil Sie nicht wissen , was zu produzieren Maschinencode für . JIT-Compiler können in diesem Bereich einige Arbeiten ausführen, sind jedoch nie mit C ++ vergleichbar, da der JIT-Compiler einfach keine Zeit und keinen Speicher für die Ausführung aufwenden kann, da dies Zeit und Speicher ist, die Sie nicht für die Ausführung Ihres Programms aufwenden, und es gibt strenge Grenzen für die Ausführung Sie können erreichen, ohne die dynamische Sprachsemantik zu brechen.
Ich werde nicht behaupten, dass dies ein inakzeptabler Kompromiss ist. Für die Natur von Python ist es jedoch von grundlegender Bedeutung, dass echte Implementierungen niemals so schnell sind wie C ++ - Implementierungen.
quelle
Es gibt drei Hauptfaktoren, die die Leistung aller dynamischen Sprachen beeinflussen, einige mehr als andere.
Für C / C ++ sind die relativen Kosten dieser 3 Faktoren nahezu Null. Anweisungen werden direkt vom Prozessor ausgeführt, der Versand dauert höchstens ein oder zwei Indirektionen, der Heap-Speicher wird niemals zugewiesen, es sei denn, Sie sagen dies. Gut geschriebener Code kann sich der Assemblersprache nähern.
Für C # / Java mit JIT-Kompilierung sind die ersten beiden niedrig, aber der gesammelte Speicherplatz ist mit Kosten verbunden. Gut geschriebener Code kann sich 2x C / C ++ nähern.
Für Python / Ruby / Perl sind die Kosten aller drei Faktoren relativ hoch. Denken Sie 5x im Vergleich zu C / C ++ oder schlechter. (*)
Denken Sie daran, dass der Code der Laufzeitbibliothek möglicherweise in derselben Sprache wie Ihre Programme geschrieben ist und dieselben Leistungseinschränkungen aufweist.
(*) Wenn die Just-In_Time (JIT) -Kompilierung auf diese Sprachen erweitert wird, nähern sich auch sie (normalerweise 2x) der Geschwindigkeit von gut geschriebenem C / C ++ - Code.
Es sollte auch beachtet werden, dass, sobald die Lücke eng ist (zwischen konkurrierenden Sprachen), Unterschiede von Algorithmen und Implementierungsdetails dominiert werden. JIT-Code schlägt möglicherweise C / C ++ und C / C ++ schlägt möglicherweise die Assemblersprache, weil es einfach einfacher ist, guten Code zu schreiben.
quelle
Hash
Klasse (eine der Kerndatenstrukturen in Ruby) in Ruby geschrieben und arbeitet vergleichbar, manchmal sogar schneller alsHash
die in C geschriebene YARV- Klasse. Einer der Gründe ist, dass große Teile der Laufzeit von Rubinius System sind in Ruby geschrieben, damit sie…Nein. Es ist nur eine Frage des Geldes und der Ressourcen, die in die schnelle Ausführung von C ++ fließen, im Vergleich zu Geld und Ressourcen, die in die schnelle Ausführung von Python fließen.
Als beispielsweise die Self VM herauskam, war sie nicht nur die schnellste dynamische OO-Sprache, sondern auch die schnellste OO-Sprachperiode. Obwohl es sich um eine unglaublich dynamische Sprache handelt (viel mehr als beispielsweise Python, Ruby, PHP oder JavaScript), war sie schneller als die meisten verfügbaren C ++ - Implementierungen.
Aber dann hat Sun das Self-Projekt (eine ausgereifte OO-Allzwecksprache für die Entwicklung großer Systeme) abgebrochen, um sich auf eine kleine Skriptsprache für animierte Menüs in TV-Set-Top-Boxen zu konzentrieren (Sie haben vielleicht davon gehört, es heißt Java) mehr Finanzierung. Gleichzeitig haben Intel, IBM, Microsoft, Sun, Metrowerks, HP et al. hat viel Geld und Ressourcen ausgegeben, um C ++ schnell zu machen. CPU-Hersteller haben ihren Chips Funktionen hinzugefügt, um C ++ schnell zu machen. Betriebssysteme wurden geschrieben oder geändert, um C ++ schnell zu machen. C ++ ist also schnell.
Ich bin mit Python nicht sonderlich vertraut, ich bin eher eine Ruby-Person, daher werde ich ein Beispiel von Ruby geben: Die
Hash
Klasse (entspricht in Funktion und Bedeutung derdict
in Python) in der Rubinius Ruby-Implementierung ist in 100% reinem Ruby geschrieben; Dennoch konkurriert es günstig und übertrifft manchmal sogar dieHash
Klasse in YARV, die in handoptimiertem C geschrieben ist. Und im Vergleich zu einigen kommerziellen Lisp- oder Smalltalk-Systemen (oder der oben genannten Self-VM) ist Rubinius 'Compiler nicht einmal so clever .Python enthält nichts, was es langsam macht. Es gibt Funktionen in heutigen Prozessoren und Betriebssystemen, die Python schaden (z. B. ist bekannt, dass der virtuelle Speicher für die Leistung der Speicherbereinigung schrecklich ist). Es gibt Funktionen, die C ++ helfen, aber Python nicht helfen (moderne CPUs versuchen, Cache-Fehler zu vermeiden, weil sie so teuer sind. Leider ist es schwierig, Cache-Fehler zu vermeiden, wenn Sie über OO und Polymorphismus verfügen. Stattdessen sollten Sie die Cache-Kosten senken vermisst. Die Azul Vega CPU, die für Java entwickelt wurde, macht das.)
Wenn Sie so viel Geld, Forschung und Ressourcen für die schnelle Erstellung von Python ausgeben wie für C ++, und Sie so viel Geld, Forschung und Ressourcen für die Erstellung von Betriebssystemen ausgeben, mit denen Python-Programme schnell ausgeführt werden, wie dies für C ++ getan wurde, und Sie wie für ausgeben Viel Geld, Forschung und Ressourcen für die Erstellung von CPUs, mit denen Python-Programme schnell ausgeführt werden, wie dies für C ++ der Fall war. Ich bin mir sicher, dass Python eine vergleichbare Leistung wie C ++ erzielen kann.
Wir haben mit ECMAScript gesehen, was passieren kann, wenn nur ein Spieler die Leistung ernst nimmt. Innerhalb eines Jahres konnten wir bei allen großen Anbietern eine 10-fache Leistungssteigerung erzielen.
quelle