Ich frage mich, ob es möglich ist, Compiler für dynamische Sprachen wie Ruby zu erstellen, die eine ähnliche und vergleichbare Leistung wie C / C ++ aufweisen. Nach meinem Verständnis von Compilern, zum Beispiel Ruby, kann das Kompilieren von Ruby-Code niemals effizient sein, da die Art und Weise, wie Ruby mit Reflektionen umgeht, Features wie die automatische Typkonvertierung von Ganzzahlen in große Ganzzahlen und das Fehlen statischer Typisierung das Erstellen eines effizienten Compilers erschweren für Ruby extrem schwer.
Ist es möglich, einen Compiler zu erstellen, der Ruby oder andere dynamische Sprachen zu einer Binärdatei kompilieren kann, die C / C ++ sehr nahe kommt? Gibt es einen fundamentalen Grund, warum JIT-Compiler wie PyPy / Rubinius irgendwann oder nie mit C / C ++ in der Leistung mithalten können?
Hinweis: Ich verstehe, dass „Leistung“ vage sein kann. Um das zu klären, habe ich gemeint, wenn Sie X in C / C ++ mit Leistung Y ausführen können, können Sie X in Ruby / Python mit Leistung nahe Y ausführen? Wobei X von Gerätetreibern und Betriebssystemcode bis hin zu Webanwendungen alles ist.
quelle
Antworten:
Allen, die mit „Ja“ geantwortet haben, biete ich einen Kontrapunkt an, bei dem die Antwort „Nein“ lautet . Diese Sprachen werden niemals mit der Leistung statisch kompilierter Sprachen mithalten können.
Kos bot den (sehr gültigen) Punkt an, dass dynamische Sprachen zur Laufzeit mehr Informationen über das System haben, die zur Optimierung des Codes verwendet werden können.
Es gibt jedoch eine andere Seite der Medaille: Diese zusätzlichen Informationen müssen nachverfolgt werden. Auf modernen Architekturen ist dies ein Leistungskiller.
William Edwards bietet einen schönen Überblick über das Argument .
Insbesondere die von Kos erwähnten Optimierungen können nur in sehr begrenztem Umfang angewendet werden, wenn Sie die Ausdruckskraft Ihrer Sprachen, wie von Devin erwähnt, drastisch einschränken. Dies ist natürlich ein tragfähiger Kompromiss, aber im Interesse der Diskussion haben Sie am Ende eine statische Sprache, keine dynamische. Diese Sprachen unterscheiden sich grundlegend von Python oder Ruby, da die meisten Leute sie verstehen würden.
William zitiert einige interessante IBM-Folien :
Einige dieser Überprüfungen können nach der Analyse beseitigt werden (Hinweis: Diese Analyse benötigt auch Zeit - zur Laufzeit).
Darüber hinaus argumentiert Kos, dass dynamische Sprachen sogar die Leistung von C ++ übertreffen könnten. Die GEG kann in der Tat das Verhalten des Programms analysieren und geeignete Optimierungen vornehmen.
Aber C ++ - Compiler können das Gleiche tun! Moderne Compiler bieten eine sogenannte profilgesteuerte Optimierung an, die bei entsprechender Eingabe das Programmlaufzeitverhalten modellieren und dieselben Optimierungen anwenden kann, die für eine JIT gelten würden.
Dies hängt natürlich vom Vorhandensein realistischer Trainingsdaten ab, und außerdem kann das Programm seine Laufzeitmerkmale nicht anpassen, wenn sich das Verwendungsmuster während der Ausführung ändert. JITs können theoretisch damit umgehen. Es würde mich interessieren, wie sich dies in der Praxis auswirkt, da die JIT zur Umstellung von Optimierungen fortlaufend Nutzungsdaten sammeln müsste, was die Ausführung erneut verlangsamt.
Zusammenfassend bin ich nicht davon überzeugt, dass Laufzeit-Hotspot-Optimierungen den Aufwand für die Verfolgung von Laufzeitinformationen auf lange Sicht im Vergleich zur statischen Analyse und Optimierung überwiegen .
quelle
javac
jemals eine profilgesteuerte Optimierung durchgeführt? Nicht so weit ich weiß. Im Allgemeinen ist es nicht sinnvoll, den Compiler einer JITted-Sprache für die Optimierung gut zu machen, da die JIT damit umgehen kann (und zumindest auf diese Weise profitieren mehr Sprachen von dem Aufwand).javac
Soweit ich weiß, wurde (verständlicherweise) nie viel Aufwand in den Optimierer gesteckt (für die .NET-Sprachen ist dies definitiv der Fall).Ja. Nehmen Sie als Beispiel PyPy. Es ist eine Sammlung von Python-Code, der bei der Interpretation in der Nähe von C ausgeführt wird (nicht ganz so nah, aber auch nicht ganz so weit entfernt). Dazu führt es eine vollständige Programmanalyse des Quellcodes durch, um jeder Variablen einen statischen Typ zuzuweisen ( Details finden Sie in den Dokumenten Annotator und Rtyper ). Sobald Sie mit denselben Typinformationen wie in C ausgestattet sind, kann es dieselben Aktionen ausführen Optimierungen. Zumindest theoretisch.
Der Nachteil ist natürlich, dass nur eine Teilmenge des Python-Codes von RPython akzeptiert wird. Selbst wenn diese Einschränkung aufgehoben wird, kann im Allgemeinen nur eine Teilmenge des Python-Codes eine gute Leistung erbringen: die Teilmenge, die analysiert und mit statischen Typen versehen werden kann.
Wenn Sie Python ausreichend einschränken, können Optimierer erstellt werden, die die eingeschränkte Teilmenge nutzen und zu effizientem Code kompilieren. Dies ist kein wirklich interessanter Vorteil, in der Tat ist es bekannt. Der springende Punkt bei der Verwendung von Python (oder Ruby) war jedoch, dass wir interessante Funktionen verwenden wollten, die möglicherweise nicht gut analysiert werden und zu einer guten Leistung führen! Die interessante Frage ist also eigentlich ...
Nein.
Womit ich meine: Sicher, wenn sich Code-Läufe ansammeln, können Sie genügend Tippinformationen und Hotspots abrufen, um den gesamten Code bis hin zum Maschinencode zu kompilieren. Und vielleicht können wir erreichen, dass dies für einige Codes eine bessere Leistung als C erbringt. Ich finde das nicht sehr kontrovers. Es muss sich jedoch noch "aufwärmen" und die Leistung ist immer noch etwas weniger vorhersehbar. Für bestimmte Aufgaben, die eine konsistent und vorhersehbar hohe Leistung erfordern, ist es nicht so gut wie C oder C ++.
Die vorhandenen Leistungsdaten für Java, die sowohl über mehr Typinformationen als Python oder Ruby als auch über einen besser entwickelten JIT-Compiler als Python oder Ruby verfügen, stimmen immer noch nicht mit C / C ++ überein. Es befindet sich jedoch im selben Stadion.
quelle
Die kurze Antwort lautet: Wir wissen nicht , fragen Sie in 100 Jahren erneut. (Dann wissen wir es vielleicht immer noch nicht. Vielleicht werden wir es nie erfahren.)
Theoretisch ist das möglich. Nehmen Sie alle jemals geschriebenen Programme, übersetzen Sie sie manuell in den effizientesten Maschinencode und schreiben Sie einen Interpreter, der Quellcodes auf Maschinencodes abbildet. Dies ist möglich, da bisher nur eine begrenzte Anzahl von Programmen geschrieben wurde (und wenn mehr Programme geschrieben werden, behalten Sie die manuellen Übersetzungen bei). Dies ist natürlich auch praktisch völlig idiotisch.
Theoretisch sind Hochsprachen zwar in der Lage, die Leistung von Maschinencode zu erreichen, werden diese jedoch nicht übertreffen. Dies ist immer noch sehr theoretisch, da wir praktisch sehr selten auf das Schreiben von Maschinencode zurückgreifen. Dieses Argument gilt nicht für den Vergleich übergeordneter Sprachen: Es bedeutet nicht, dass C effizienter als Python sein muss, nur, dass Maschinencode nicht schlechter als Python sein kann.
Wenn wir von der anderen Seite kommen, sehen wir rein experimentell, dass interpretierte Hochsprachen die meiste Zeit schlechter abschneiden als kompilierte Niedrigsprachen. Wir neigen dazu, nicht zeitkritischen Code in sehr höheren Sprachen und zeitkritischen inneren Schleifen in Assembler zu schreiben, wobei Sprachen wie C und Python dazwischen liegen. Ich habe zwar keine Statistiken, um dies zu sichern, aber in den meisten Fällen ist dies in der Tat die beste Entscheidung.
Es gibt jedoch unbestrittene Fälle, in denen Hochsprachen den Code übertreffen, den man realistisch schreiben würde: spezielle Programmierumgebungen. Programme wie Matlab und Mathematica sind oft weitaus besser in der Lage, bestimmte Arten von mathematischen Problemen zu lösen, als das, was nur Sterbliche schreiben können. Die Bibliotheksfunktionen wurden möglicherweise in C oder C ++ geschrieben (was für die "Sprachen auf niedriger Ebene sind effizienter"), aber das geht mich nichts an, wenn ich Mathematica-Code schreibe, ist die Bibliothek eine Black Box.
Ist es theoretisch möglich, dass Python der optimalen Leistung so nahe oder sogar noch näher kommt als C? Wie oben gesehen, ja, aber davon sind wir heute sehr weit entfernt. Andererseits haben die Compiler in den letzten Jahrzehnten große Fortschritte gemacht, und diese Fortschritte werden nicht langsamer.
Hochsprachen machen in der Regel mehr Dinge automatisch, so dass sie mehr Arbeit haben und daher weniger effizient sind. Auf der anderen Seite enthalten sie tendenziell mehr semantische Informationen, sodass es einfacher ist, Optimierungen zu erkennen (wenn Sie einen Haskell-Compiler schreiben, müssen Sie sich keine Sorgen machen, dass ein anderer Thread eine Variable unter Ihrer Nase ändert). Eine von mehreren Versuchen,
Äpfel und Orangen inverschiedenen Programmiersprachen zu vergleichen, ist das Computersprachen-Benchmark-Spiel (früher als Shootout bekannt). Fortran neigt dazu, bei numerischen Aufgaben zu glänzen; aber wenn es darum geht, strukturierte Daten oder hochratige Thread-Kommutierungen zu manipulieren, F # und Scala machen es gut. Nehmen Sie diese Ergebnisse nicht als Evangelium: Vieles, was sie messen, ist, wie gut der Autor des Testprogramms in jeder Sprache war.Ein Argument für Hochsprachen ist, dass die Leistung auf modernen Systemen nicht so stark mit der Anzahl der ausgeführten Befehle korreliert, und dies im Laufe der Zeit weniger. Low-Level-Sprachen eignen sich gut für einfache sequentielle Maschinen. Wenn eine Hochsprache doppelt so viele Anweisungen ausführt, den Cache jedoch intelligenter nutzt, so dass nur halb so viele Cache-Fehler auftreten, kann dies zum Sieger werden.
Auf Server- und Desktop-Plattformen haben CPUs fast ein Plateau erreicht, auf dem sie nicht schneller werden (auch mobile Plattformen sind auf dem Weg dorthin). Dies begünstigt Sprachen, in denen Parallelität leicht auszunutzen ist. Viele Prozessoren warten die meiste Zeit auf eine E / A-Antwort. Die für die Berechnung aufgewendete Zeit ist im Vergleich zur Anzahl der E / A-Vorgänge von geringer Bedeutung, und eine Sprache, mit der der Programmierer die Kommunikation minimieren kann, ist von Vorteil.
Alles in allem haben Hochsprachen, die mit einer Strafe beginnen, mehr Raum für Verbesserungen. Wie nah können sie kommen? Fragen Sie noch einmal in 100 Jahren.
Schlussbemerkung: Häufig erfolgt der Vergleich nicht zwischen dem effizientesten Programm, das in Sprache A geschrieben werden kann, und demselben in Sprache B, noch zwischen dem effizientesten Programm, das jemals in jeder Sprache geschrieben wurde, sondern zwischen dem effizientesten Programm, das geschrieben werden kann von einem Menschen in einer bestimmten Zeit in jeder Sprache. Dies führt ein Element ein, das selbst im Prinzip nicht mathematisch analysiert werden kann. In der Praxis bedeutet dies häufig, dass die beste Leistung ein Kompromiss zwischen der Menge an Code auf niedriger Ebene, die Sie zum Erreichen der Leistungsziele schreiben müssen, und der Menge an Code auf niedriger Ebene ist, für die Sie Zeit haben, um die Veröffentlichungstermine einzuhalten.
quelle
Der grundlegende Unterschied zwischen der C ++ Anweisung
x = a + b
und der Python - Anweisungx = a + b
ist , dass ein C / C ++ Compiler von dieser Aussage sagen können (und eine wenig mehr Informationen , die es über die Arten von leicht verfügbar istx
,a
undb
) genau das, was Code - Maschine muss ausgeführt werden . Um zu bestimmen, welche Operationen die Python-Anweisung ausführen soll, müssen Sie das Halteproblem lösen.In C wird diese Anweisung im Grunde genommen zu einer von wenigen Arten von Maschinenadditionen kompiliert (und der C-Compiler weiß, welche). In C ++ kann es auf diese Weise kompiliert werden, oder es kann kompiliert werden, um eine statisch bekannte Funktion aufzurufen, oder (im schlimmsten Fall) es muss kompiliert werden, um eine virtuelle Methode zu suchen und aufzurufen, aber selbst dies hat einen relativ geringen Maschinencode-Aufwand. Noch wichtiger ist jedoch, dass der C ++ - Compiler anhand der statisch bekannten Typen erkennen kann, ob er eine einzelne schnelle Additionsoperation ausführen kann oder ob er eine der langsameren Optionen verwenden muss.
In Python könnte ein Compiler tun theoretisch fast so gut , wenn sie wusste , dass
a
undb
waren beideint
s. Es gibt einen zusätzlichen Boxing-Aufwand, aber wenn Typen statisch bekannt wären, könnten Sie diesen wahrscheinlich auch beseitigen (während Sie weiterhin die Schnittstelle präsentieren, dass Ganzzahlen Objekte mit Methoden, Hierarchien von Superklassen usw. sind). Das Problem ist, dass ein Compiler für Python nicht kannDies ist bekannt, da Klassen zur Laufzeit definiert werden, zur Laufzeit geändert werden können und sogar die Module, die das Definieren und Importieren ausführen, zur Laufzeit aufgelöst werden (und selbst welche Importanweisungen ausgeführt werden, hängt von Dingen ab, die nur zur Laufzeit bekannt sind). Der Python-Compiler müsste also wissen, welcher Code ausgeführt wurde (dh, das Halting-Problem lösen), um zu wissen, was die Anweisung, die er kompiliert, bewirkt.Selbst mit den ausgefeiltesten Analysen, die theoretisch möglich sind , können Sie einfach nicht viel darüber sagen, was eine bestimmte Python-Anweisung im Voraus tun wird. Dies bedeutet, dass selbst wenn ein ausgefeilter Python-Compiler implementiert wäre, in fast allen Fällen noch Maschinencode ausgegeben werden müsste, der dem Python-Dictionary-Lookup-Protokoll folgt, um die Klasse eines Objekts zu bestimmen und Methoden zu finden (MRO der Klassenhierarchie durchqueren, Dies kann sich auch dynamisch zur Laufzeit ändern und ist daher schwierig in eine einfache virtuelle Methodentabelle zu kompilieren. Im Grunde tun sie das, was die (langsamen) Interpreter tun. Aus diesem Grund gibt es keine ausgeklügelten Optimierungscompiler für dynamische Sprachen. Es ist nicht nur schwer, eine zu erstellen, die maximal mögliche Auszahlung ist nicht
Beachten Sie, dass dies nicht auf basiert , was der Code ist zu tun, es ist auf das, was der Code könnte tun. Sogar Python-Code, der eine einfache Folge von Ganzzahl-Arithmetikoperationen ist, muss so kompiliert werden, als würde er beliebige Klassenoperationen aufrufen. Statische Sprachen haben größere Einschränkungen in Bezug auf die Möglichkeiten, die der Code bieten kann, und folglich können ihre Compiler mehr Annahmen treffen.
JIT-Compiler profitieren davon, indem sie auf die Laufzeit warten, um zu kompilieren / optimieren. Auf diese Weise können sie Code emittieren , die für funktioniert , was der Code ist zu tun , anstatt was es tun könnte. Und aus diesem Grund haben JIT-Compiler für dynamische Sprachen eine viel größere potenzielle Auszahlung als für statische Sprachen. Für statischere Sprachen kann vieles, was ein Optimierer wissen möchte, im Voraus bekannt sein. Sie können es also genauso gut optimieren, sodass ein JIT-Compiler weniger Zeit hat.
Es gibt verschiedene JIT-Compiler für dynamische Sprachen, die behaupten, Ausführungsgeschwindigkeiten zu erreichen, die mit denen von kompiliertem und optimiertem C / C ++ vergleichbar sind. Es gibt sogar Optimierungen, die von einem JIT-Compiler vorgenommen werden können, die von einem vorzeitigen Compiler für keine Sprache durchgeführt werden können, sodass theoretisch die JIT-Kompilierung (für einige Programme) eines Tages den bestmöglichen statischen Compiler übertreffen könnte. Wie Devin jedoch zutreffend hervorhob, sind die Eigenschaften der JIT-Kompilierung (nur die "Hotspots" sind schnell und erst nach einer Aufwärmphase) so, dass JIT-kompilierte dynamische Sprachen wahrscheinlich nicht für alle möglichen Anwendungen geeignet sind, selbst wenn sie es werden so schnell oder schneller als statisch kompilierte Sprachen im Allgemeinen.
quelle
foo = x + y
das Verhalten des Additionsoperators zur Kompilierungszeit von der Lösung des Halteproblems abhängt.x + y
effizienten Maschinenhinzufügungsoperationen zu kompilieren , müssen Sie beim Kompilieren wissen, ob dies der Fall ist oder nicht. Die ganze Zeit , nicht nur ein Teil der Zeit. Für dynamische Sprachen ist dies mit realistischen Programmen so gut wie nie möglich, obwohl vernünftige Heuristiken die meiste Zeit richtig raten würden. Compilation erfordert Compile-Zeit garantiert . Wenn Sie also von "unter vielen Umständen" sprechen, sprechen Sie meine Antwort überhaupt nicht an.Nur ein kurzer Hinweis, der das Worst-Case-Szenario für dynamische Sprachen umreißt:
Infolgedessen kann (volles) Perl niemals statisch kompiliert werden.
Generell kommt es wie immer darauf an. Ich bin zuversichtlich, dass wenn Sie versuchen, dynamische Features in einer statisch kompilierten Sprache zu emulieren, durchdachte Interpreten oder (teilweise) kompilierte Varianten die Leistung statisch kompilierter Sprachen annähern oder unterschreiten können.
Ein weiterer zu beachtender Punkt ist, dass dynamische Sprachen ein anderes Problem als C lösen . C ist für Assembler kaum mehr als eine nette Syntax, während dynamische Sprachen reichhaltige Abstraktionen bieten. Die Laufzeitleistung ist oft nicht das Hauptanliegen: Die Zeit bis zur Markteinführung hängt beispielsweise davon ab, dass Ihre Entwickler in der Lage sind, komplexe, qualitativ hochwertige Systeme in kurzer Zeit zu schreiben. Erweiterbarkeit ohne Neukompilierung, zum Beispiel mit Plugins, ist ein weiteres beliebtes Feature. Welche Sprache bevorzugen Sie in diesen Fällen?
quelle
In dem Versuch, eine objektivere wissenschaftliche Antwort auf diese Frage zu geben, argumentiere ich wie folgt. Für eine dynamische Sprache ist ein Interpreter oder eine Laufzeit erforderlich, um zur Laufzeit Entscheidungen treffen zu können. Dieser Interpreter oder diese Laufzeit ist ein Computerprogramm und wurde als solches in einer Programmiersprache geschrieben, entweder statisch oder dynamisch.
Wenn der Interpreter / die Laufzeit in einer statischen Sprache geschrieben wurde, könnte man ein Programm in dieser statischen Sprache schreiben, das (a) dieselbe Funktion wie das dynamische Programm ausführt, das es interpretiert, und (b) mindestens ebenso ausführt. Dies ist hoffentlich selbstverständlich, da ein strenger Nachweis dieser Behauptungen zusätzlichen (möglicherweise erheblichen) Aufwand erfordern würde.
Unter der Annahme, dass diese Behauptungen wahr sind, besteht der einzige Ausweg darin, dass der Interpreter / die Laufzeit auch in einer dynamischen Sprache geschrieben sein muss. Wir haben jedoch das gleiche Problem wie zuvor: Wenn der Interpreter dynamisch ist, ist ein Interpreter / eine Laufzeit erforderlich, der / die ebenfalls in einer Programmiersprache (dynamisch oder statisch) geschrieben sein muss.
Wenn Sie nicht davon ausgehen, dass eine Instanz eines Interpreters zur Laufzeit in der Lage ist, sich selbst zu interpretieren (ich hoffe, dies ist offensichtlich absurd), besteht die einzige Möglichkeit, statische Sprachen zu schlagen, darin, dass jede Interpreterinstanz von einer separaten Interpreterinstanz interpretiert wird. Dies führt entweder zu einem unendlichen Rückschritt (ich hoffe, dass dies selbstverständlich absurd ist) oder zu einem geschlossenen Kreis von Dolmetschern (ich hoffe, dass dies ebenfalls selbstverständlich absurd ist).
Es scheint also, dass dynamische Sprachen auch theoretisch keine bessere Leistung erbringen können als statische Sprachen im Allgemeinen. Bei der Verwendung von Modellen realistischer Computer erscheint dies noch plausibler. Schließlich kann eine Maschine nur Sequenzen von Maschinenbefehlen ausführen, und alle Sequenzen von Maschinenbefehlen können statisch kompiliert werden.
In der Praxis kann es erforderlich sein, den Interpreter / die Laufzeit in einer statischen Sprache erneut zu implementieren, um die Leistung einer dynamischen Sprache mit einer statischen Sprache abzugleichen. Es ist jedoch der springende Punkt dieses Arguments, dass Sie dies überhaupt tun können. Es ist eine Henne-Ei-Frage, und vorausgesetzt, Sie stimmen den oben gemachten unbewiesenen (meiner Meinung nach meist selbstverständlichen) Annahmen zu, können wir sie tatsächlich beantworten. Wir müssen den statischen, nicht den dynamischen Sprachen das Nicken geben.
Ein anderer Weg, die Frage im Lichte dieser Diskussion zu beantworten, ist der folgende: In dem gespeicherten Programm control = data model of computing, das dem modernen Computing zugrunde liegt, ist die Unterscheidung zwischen statischer und dynamischer Kompilierung eine falsche Zweiteilung; Statisch kompilierte Sprachen müssen die Möglichkeit haben, zur Laufzeit beliebigen Code zu generieren und auszuführen. Es hängt im Wesentlichen mit der universellen Berechnung zusammen.
quelle
main(args) { for ( i=0; i<1000000; i++ ) { if ( args[0] == "1" ) {...} else {...} }
kann erheblich beschleunigt werden, sobald der Wert vonargs
bekannt ist (vorausgesetzt, er ändert sich nie, was wir möglicherweise behaupten können). Ein statischer Compiler kann keinen Code erstellen, der den Vergleich jemals verwirft. (Natürlich ziehen Sie in diesem Beispiel nur den Ringif
aus der Schleife. Aber das Ding ist möglicherweise komplizierter.)Ich denke, dass die Antwort "Ja" ist . Ich glaube auch, dass sie die aktuelle C / C ++ - Architektur in Bezug auf Effizienz sogar übertreffen können (wenn auch geringfügig).
Der Grund ist einfach: Es gibt mehr Informationen zur Laufzeit als zur Kompilierungszeit.
Dynamische Typen sind nur ein kleines Hindernis: Wenn eine Funktion immer oder fast immer mit denselben Argumenttypen ausgeführt wird, kann ein JIT-Optimierer einen Verzweigungs- und Maschinencode für diesen speziellen Fall generieren. Und es gibt noch so viel mehr zu tun.
Siehe Dynamic Languages Strike Back , eine Rede von Steve Yegge von Google (irgendwo, glaube ich, gibt es auch eine Videoversion). Er erwähnt einige konkrete JIT-Optimierungstechniken aus V8. Inspirierend!
Ich freue mich auf das, was wir in den nächsten 5 Jahren haben werden!
quelle
Leute, die anscheinend denken, dass dies theoretisch möglich ist oder in ferner Zukunft, sind meiner Meinung nach völlig falsch. Der Punkt liegt in der Tatsache, dass dynamische Sprachen einen völlig anderen Programmierstil bieten und auferlegen. Tatsächlich gibt es zwei Unterschiede, auch wenn beide Aspekte zusammenhängen:
Der zweite Punkt ist die kostenlose Generizität. Beachten Sie, dass Strukturen hier zusammengesetzte Elemente, Auflistungen, aber auch Typen selbst und sogar (!) Routinen aller Art (Funktionen, Aktionen, Operationen) sind. Wir könnten Strukturen anhand ihrer Elementtypen eingeben, aber aufgrund des ersten Punktes des check würde sowieso zur Laufzeit passieren. Wir könnten Symbole getippt haben und noch strukturierte Symbole haben, die entsprechend ihren Elementtypen untypisiert sind (ein Array
a
würde nur als Array und nicht als Array von Ints getippt werden), aber selbst diese wenigen sind in einer dynamischen Sprache nicht wahr (a
könnten auch enthalten) ein Faden).Element
Element
Es ist für mich klar, dass dies nur eine enorme Leistungsstrafe ist; und ich berühre nicht einmal alle Konsequenzen (die unzähligen Laufzeitprüfungen aller Art, die notwendig sind, um die Sensibilität des Programms sicherzustellen), die in anderen Posts gut beschrieben wurden.
quelle
Ich hatte nicht die Zeit, alle Antworten im Detail zu lesen ... aber ich war amüsiert.
In den sechziger und frühen siebziger Jahren gab es eine ähnliche Kontroverse (die Geschichte der Informatik wiederholt sich häufig): Können Hochsprachen kompiliert werden, um Code zu erzeugen, der so effizient ist wie der von einem Programmierer manuell erzeugte Maschinencode, also Assembler-Code. Jeder weiß, dass ein Programmierer viel schlauer ist als jedes andere Programm und sich eine sehr clevere Optimierung einfallen lassen kann. Das ist natürlich Ironie von meiner Seite.
Es gab sogar ein Konzept der Codeerweiterung: das Verhältnis der Größe des von einem Compiler erstellten Codes zur Größe des Codes für dasselbe Programm, das von einem guten Programmierer erstellt wurde (als ob es zu viele davon gegeben hätte :-). Die Idee war natürlich, dass dieses Verhältnis immer größer als 1 war. Die damaligen Sprachen waren Cobol und Fortran 4 oder Algol 60 für die Intellektuellen. Ich glaube, Lisp wurde nicht berücksichtigt.
Nun, es gab einige Gerüchte, dass jemand einen Compiler entwickelt hat, der manchmal eine Expansionsrate von 1 hat ... bis es einfach zur Regel wurde, dass kompilierter Code viel besser ist als handgeschriebener Code (und auch zuverlässiger). Die Leute machten sich damals Sorgen um die Codegröße (kleine Speicher), aber das Gleiche gilt für die Geschwindigkeit oder den Energieverbrauch. Ich werde nicht auf die Gründe eingehen.
Merkwürdige Merkmale, dynamische Merkmale einer Sprache spielen keine Rolle. Entscheidend ist, wie sie verwendet werden, ob sie verwendet werden. Die Leistung, in welcher Einheit auch immer (Codegröße, Geschwindigkeit, Energie, ...), hängt häufig von sehr kleinen Programmteilen ab. Daher besteht eine gute Chance, dass Einrichtungen, die Ausdruckskraft verleihen, nicht wirklich im Weg stehen. Mit guter Programmierpraxis werden fortschrittliche Einrichtungen nur auf disziplinierte Weise verwendet, um sich neue Strukturen vorzustellen (das war die Lektion für Lispes).
Die Tatsache, dass eine Sprache keine statische Typisierung aufweist, hat niemals dazu geführt, dass in dieser Sprache geschriebene Programme nicht statisch typisiert sind. Andererseits kann es sein, dass das von einem Programm verwendete Typsystem noch nicht ausreichend formalisiert ist, um einen Typprüfer zu erstellen.
In der Diskussion gab es mehrere Hinweise auf die Worst-Case-Analyse ("Stop-Problem", PERL-Analyse). Die Worst-Case-Analyse spielt jedoch meist keine Rolle. Was zählt, ist, was in den meisten Fällen oder in nützlichen Fällen passiert ... wie auch immer definiert oder verstanden oder erfahren. Hier kommt eine andere Geschichte, die direkt mit der Programmoptimierung zusammenhängt. Es fand vor langer Zeit an einer großen Universität in Texas statt, zwischen einem Doktoranden und seinem Berater (der später an einer der nationalen Akademien gewählt wurde). Soweit ich mich erinnere, bestand der Student darauf, ein Analyse- / Optimierungsproblem zu untersuchen, das der Berater als nicht nachvollziehbar erwiesen hatte. Bald sprachen sie nicht mehr miteinander. Aber der Student hatte Recht: Das Problem war in den meisten praktischen Fällen so leicht zu lösen, dass die von ihm erstellte Dissertation zum Nachschlagewerk wurde.
Und um die Aussage weiter zu kommentieren, dass
Perl parsing is not computable
, was auch immer mit diesem Satz gemeint ist, es ein ähnliches Problem mit ML gibt, das eine bemerkenswert gut formalisierte Sprache ist.Type checking complexity in ML is a double exponential in the lenght of the program.
Das ist ein sehr präzises und formales Ergebnis in der Worst-Case-Komplexität ... das spielt überhaupt keine Rolle. Afaik, ML-Benutzer warten noch auf ein praktisches Programm, das die Typprüfung explodieren lässt.In vielen Fällen ist die menschliche Zeit und Kompetenz nach wie vor knapper als die Rechenleistung.
Das eigentliche Problem der Zukunft wird darin bestehen, unsere Sprachen so weiterzuentwickeln, dass neues Wissen und neue Programmierformen integriert werden, ohne dass die gesamte noch verwendete Legacy-Software neu geschrieben werden muss.
Wenn man sich die Mathematik ansieht, ist es eine sehr große Wissensbasis. Die verwendeten Sprachen, Notationen und Konzepte haben sich im Laufe der Jahrhunderte weiterentwickelt. Es ist einfach, alte Sätze mit den neuen Konzepten zu schreiben. Wir passen die Hauptbeweise an, kümmern uns aber nicht um viele Ergebnisse.
Bei der Programmierung müssen wir jedoch möglicherweise alle Beweise von Grund auf neu schreiben (Programme sind Beweise). Es kann sein, dass wir wirklich sehr gute und entwicklungsfähige Programmiersprachen brauchen. Optimierer-Designer werden gerne folgen.
quelle
Ein paar Notizen:
Nicht alle Hochsprachen sind dynamisch. Haskell hat ein sehr hohes Niveau, ist jedoch vollständig statisch typisiert. Selbst Systemprogrammiersprachen wie Rust, Nim und D können Abstraktionen auf hoher Ebene kurz und effizient ausdrücken. Tatsächlich können sie so prägnant sein wie dynamische Sprachen.
Es gibt hochoptimierte Frühkompilierer für dynamische Sprachen. Gute Lisp-Implementierungen erreichen die Hälfte der Geschwindigkeit von Äquivalent C.
Die JIT-Kompilierung kann hier ein großer Gewinn sein. Die Web Application Firewall von CloudFlare generiert Lua-Code, der von LuaJIT ausgeführt wird. LuaJIT optimiert die tatsächlich verwendeten Ausführungspfade (normalerweise die Nichtangriffspfade) erheblich, so dass der Code viel schneller ausgeführt wird als der Code, der von einem statischen Compiler auf der tatsächlichen Arbeitslast erstellt wird. Im Gegensatz zu einem statischen Compiler mit profilgesteuerter Optimierung passt sich LuaJIT zur Laufzeit an veränderte Ausführungspfade an.
Die Deoptimierung ist ebenfalls von entscheidender Bedeutung. Anstatt dass JIT-kompilierter Code nach einer Klasse suchen muss, die monkeypatched ist, löst der Vorgang des monkeypatching einen Hook im Laufzeitsystem aus, der den von der alten Definition abhängigen Maschinencode verwirft.
quelle