Sie haben Guidos Aussage bei der Formulierung Ihrer Frage zu stark vereinfacht. Das Problem besteht nicht darin, einen Compiler für eine dynamisch typisierte Sprache zu schreiben. Das Problem besteht darin, eine zu schreiben, die (Kriterium 1) immer korrekt ist, (Kriterium 2) die dynamische Typisierung beibehält und (Kriterium 3) für eine erhebliche Menge Code spürbar schneller ist.
Es ist einfach, 90% (fehlgeschlagene Kriterien 1) von Python zu implementieren und konstant schnell zu sein. Ebenso ist es einfach, eine schnellere Python-Variante mit statischer Typisierung zu erstellen (fehlgeschlagene Kriterien 2). Die Implementierung von 100% ist ebenfalls einfach (sofern die Implementierung einer so komplexen Sprache einfach ist), aber bisher ist jede einfache Implementierung relativ langsam (fehlgeschlagene Kriterien 3).
Die Implementierung eines Interpreters plus JIT , der korrekt ist, die gesamte Sprache implementiert und für einige Codes schneller ist, erweist sich als machbar, wenn auch erheblich schwieriger (vgl. PyPy) und nur dann, wenn Sie die Erstellung des JIT-Compilers automatisieren (Psyco hat darauf verzichtet) , war aber sehr begrenzt in welchem Code es beschleunigen konnte). Beachten Sie jedoch, dass dies ausdrücklich außerhalb des Geltungsbereichs liegt, da es sich um statische Aufladung handelt(auch bekannt als vorzeitig) Compiler. Ich erwähne dies nur, um zu erklären, warum sein Ansatz für statische Compiler nicht funktioniert (oder zumindest kein Gegenbeispiel vorhanden ist): Er muss zuerst das Programm interpretieren und beobachten und dann Code für eine bestimmte Iteration einer Schleife (oder eines anderen linearen Codes) generieren Pfad), dann optimieren Sie die Hölle daraus basierend auf Annahmen, die nur für diese bestimmte Iteration gelten (oder zumindest nicht für alle möglichen Iterationen). Die Erwartung ist, dass viele spätere Ausführungen dieses Codes ebenfalls der Erwartung entsprechen und somit von den Optimierungen profitieren. Einige (relativ billige) Prüfungen werden hinzugefügt, um die Richtigkeit sicherzustellen. Um all dies zu tun, benötigen Sie eine Vorstellung davon, worauf Sie sich spezialisieren müssen, und eine langsame, aber allgemeine Implementierung, auf die Sie zurückgreifen können. AOT-Compiler haben keine. Sie können sich überhaupt nicht spezialisierenbasierend auf Code, den sie nicht sehen können (z. B. dynamisch geladener Code), und nachlässige Spezialisierung bedeutet, mehr Code zu generieren, was eine Reihe von Problemen mit sich bringt (Icache-Nutzung, Binärgröße, Kompilierungszeit, zusätzliche Verzweigungen).
Die Implementierung eines AOT-Compilers, der die gesamte Sprache korrekt implementiert, ist ebenfalls relativ einfach: Generieren Sie Code, der zur Laufzeit aufgerufen wird, um das zu tun, was der Interpreter tun würde, wenn er mit diesem Code gespeist würde. Nuitka macht das (meistens). Dies bringt jedoch keinen großen Leistungsvorteil (Fehlerkriterium 3), da Sie immer noch genauso viel unnötige Arbeit wie ein Interpreter leisten müssen, außer den Bytecode an den C-Code-Block zu senden, der das tut, was Sie kompiliert haben Das sind nur relativ geringe Kosten - signifikant genug, um in einem vorhandenen Interpreter optimiert zu werden, aber nicht signifikant genug, um eine völlig neue Implementierung mit eigenen Problemen zu rechtfertigen.
Was wäre erforderlich, um alle drei Kriterien zu erfüllen? Wir haben keine Ahnung. Es gibt einige statische Analyseschemata, mit denen Informationen zu konkreten Typen, Kontrollabläufen usw. aus Python-Programmen extrahiert werden können. Diejenigen, die genaue Daten liefern, die über den Rahmen eines einzelnen Basisblocks hinausgehen, sind extrem langsam und müssen das gesamte Programm oder zumindest den größten Teil davon sehen. Trotzdem können Sie mit diesen Informationen nicht viel anfangen, außer vielleicht ein paar Operationen für eingebaute Typen zu optimieren.
Warum ist das? Um es ganz klar auszudrücken: Ein Compiler entfernt entweder die Möglichkeit, zur Laufzeit geladenen Python-Code auszuführen (fehlgeschlagenes Kriterium 1), oder er trifft keine Annahmen, die von einem Python-Code überhaupt ungültig gemacht werden können. Leider beinhaltet dies so ziemlich alles, was für die Optimierung von Programmen nützlich ist: Globale einschließlich Funktionen können zurückgebunden werden, Klassen können mutiert oder vollständig ersetzt werden, Module können ebenfalls willkürlich geändert werden, der Import kann auf verschiedene Arten entführt werden usw. Eine einzelne Zeichenfolge, die an übergeben wird eval
, exec
, __import__
oder zahlreiche andere Funktionen können dies tun. In der Tat bedeutet dies, dass fast keine großen Optimierungen angewendet werden können, was nur einen geringen Leistungsvorteil bringt (fehlgeschlagene Kriterien 3). Zurück zum obigen Absatz.