Ich interessiere mich für die zeitliche Komplexität eines Compilers. Dies ist natürlich eine sehr komplizierte Frage, da viele Compiler, Compileroptionen und Variablen zu berücksichtigen sind. Insbesondere interessiere ich mich für LLVM, würde mich aber für Gedanken interessieren, die Menschen hatten, oder Orte, an denen sie mit der Forschung beginnen könnten. Ein ganz Google scheint wenig ans Licht zu bringen.
Ich würde vermuten, dass es einige Optimierungsschritte gibt, die exponentiell sind, aber nur einen geringen Einfluss auf die tatsächliche Zeit haben. Exponentiale, die auf der Zahl basieren, sind z. B. Argumente einer Funktion.
Von meinem Kopf aus würde ich sagen, dass das Erzeugen des AST-Baums linear wäre. Für die IR-Erzeugung müsste der Baum schrittweise durchlaufen werden, während die Werte in ständig wachsenden Tabellen nachgeschlagen werden, also oder O ( n log n ) . Die Codegenerierung und -verknüpfung wäre eine ähnliche Art von Operation. Daher würde ich O ( n 2 ) annehmen, wenn wir Exponentiale von Variablen entfernen würden, die nicht realistisch wachsen.
Ich könnte mich jedoch völlig irren. Hat jemand irgendwelche Gedanken dazu?
Antworten:
Das beste Buch, um Ihre Frage zu beantworten, ist wahrscheinlich: Cooper und Torczon, "Engineering a Compiler", 2003. Wenn Sie Zugang zu einer Universitätsbibliothek haben, sollten Sie eine Kopie ausleihen können.
In einem Produktionscompiler wie llvm oder gcc bemühen sich die Designer, alle Algorithmen unter wobei n die Größe der Eingabe ist. Für einige der Analysen für die "Optimierungs" -Phasen bedeutet dies, dass Sie Heuristiken verwenden müssen, anstatt wirklich optimalen Code zu erzeugen.O ( n2) n
Dann wird der Analysebaum typischerweise zu einem Kontrollflussgraphen "abgeflacht". Die Knoten des Steuerflussdiagramms können Anweisungen mit drei Adressen sein (ähnlich einer RISC-Assemblersprache), und die Größe des Steuerflussdiagramms ist in der Regel linear in Bezug auf die Größe des Analysebaums.
Als nächstes kommen Sie in die Registerzuordnung. Registerzuordnung kann als formulieren Graph-Färbungsproblem , und Färbung ein Diagramm mit einer minimalen Anzahl von Farben ist bekannt, dass NP-Hard. Daher verwenden die meisten Compiler eine Art gierige Heuristik in Kombination mit Register-Spilling, um die Anzahl der Register-Spills innerhalb eines angemessenen Zeitraums so gut wie möglich zu reduzieren.
Schließlich kommen Sie in die Codegenerierung. Die Codegenerierung erfolgt typischerweise als maximaler Basisblock zu einem Zeitpunkt, an dem ein Basisblock eine Menge linear verbundener Kontrollflussgraphknoten mit einem einzelnen Eingang und einem einzelnen Ausgang ist. Dies kann als ein Diagramm umformuliert werden, das ein Problem abdeckt, bei dem das Diagramm, das Sie abdecken möchten, das Abhängigkeitsdiagramm des Satzes von Anweisungen mit drei Adressen im Basisblock ist, und Sie versuchen, einen Satz von Diagrammen abzudecken, die die verfügbare Maschine darstellen Anleitung. Dieses Problem ist exponentiell in Bezug auf die Größe des größten Basisblocks (der im Prinzip in der gleichen Größenordnung wie die Größe des gesamten Programms liegen kann), weshalb dies wiederum in der Regel mit Heuristiken durchgeführt wird, bei denen nur eine kleine Teilmenge der möglichen Abdeckungen vorhanden ist untersucht.
quelle
Tatsächlich sind einige Sprachen (wie C ++, Lisp und D) zur Kompilierungszeit vollständig, so dass das Kompilieren im Allgemeinen nicht entscheidend ist. Für C ++ liegt dies an der rekursiven Template-Instanziierung. Für Lisp und D können Sie fast jeden Code zur Kompilierungszeit ausführen, sodass Sie den Compiler in eine Endlosschleife werfen können, wenn Sie möchten.
quelle
Aus meiner tatsächlichen Erfahrung mit dem C # -Compiler kann ich sagen, dass die Größe der Ausgabebinärdatei für bestimmte Programme exponentiell in Bezug auf die Größe der Eingabequelle zunimmt (dies ist in der C # -Spezifikation tatsächlich erforderlich und kann nicht reduziert werden) muss auch mindestens exponentiell sein.
Die allgemeine Aufgabe zur Überlastungslösung in C # ist bekanntermaßen NP-hart (und die tatsächliche Implementierungskomplexität ist mindestens exponentiell).
Für die Verarbeitung von XML-Dokumentationskommentaren in C # -Quellen müssen auch beliebige XPath 1.0-Ausdrücke zur Kompilierungszeit ausgewertet werden, was ebenfalls exponentiell ist (AFAIK).
quelle
class X<A,B,C,D,E> { class Y : X<Y,Y,Y,Y,Y> { Y.Y.Y.Y.Y.Y.Y.Y.Y y; } }
Messen Sie es mit realistischen Codebasen, z. B. einer Reihe von Open Source-Projekten. Wenn Sie die Ergebnisse als (codeSize, finishTime) zeichnen, können Sie diese Diagramme zeichnen. Wenn Ihre Daten f (x) = y O (n) sind, sollte das Zeichnen von g = f (x) / x eine gerade Linie ergeben, nachdem die Daten groß werden.
Zeichnen Sie f (x) / x, f (x) / lg (x), f (x) / (x * lg (x)), f (x) / (x * x) usw. Die Grafik taucht entweder ab auf Null stellen, ungebunden erhöhen oder abflachen. Diese Idee eignet sich zum Beispiel zum Messen der Einfügezeiten ausgehend von einer leeren Datenbank (z. B. um über einen längeren Zeitraum nach einem Leistungsleck zu suchen).
quelle