2009-02-23 12 views

Antwort

1

Hier ist ein Schuss ..

Verwenden inkrementale Kompilierung, wenn Ihre Werkzeugkette unterstützt wird. (Marke, Visual Studio, etc).

Zum Beispiel, in GCC/make, wenn Sie viele Dateien zu kompilieren haben, aber nur Änderungen in einer Datei vornehmen, dann wird nur diese eine Datei kompiliert.

+0

Es sei denn, es ist eine Header-Datei. Es lohnt sich oft, ein wenig Aufwand in die Faktorisierung von .h-Dateien zu investieren, damit sie sich nicht zu oft ändern. Ein älteres Projekt haben wir unnötigerweise Daten in einer Header-Datei gespeichert, die überall verwendet wird. Dort gibt es keine inkrementellen Kompilierungsgewinne. – Edmund

+0

Uh, ich denke, Header-Dateien sind nicht kompiliert .. Wie, wenn Sie versuchen, eine Header-Datei in Visual C++ zu kompilieren, erhalten Sie eine Nachricht sagen "Kein Kompilierungstool mit dieser Erweiterung verbunden." Wie auch immer, das ist richtig, aber nur für C/C++, vielleicht .. – krebstar

3

Wie kann ich die Kompilierzeit minimieren?

  • Keine Kompilation (interpretierte Sprache)
  • Verzögerte (just in time) Kompilation
  • Incremental Compilation
  • vorkompilierte Header-Dateien
+0

Wir haben vor ein paar Jahren einen Test hier mit VS6 gemacht, und unser großes Projekt mit einer 15minütigen Kompilierzeit dauerte tatsächlich etwa 6 Sekunden * länger, um mit vorkompiliertem Header zu kompilieren Dateien aktiviert. –

+0

Nun, da ich mir diese Antwort anschaue, habe ich auch Probleme mit deinen anderen Kugeln. Das Hauptproblem ist, dass sie die Kompilierzeit nicht reduzieren, sondern stattdessen an einen anderen Ort verschieben. Das ist vielleicht das, was er braucht, aber es ist nicht das, wonach er gefragt hat. –

+0

Mit VS6 IMO können Sie es sich nicht leisten, 'auto' für den vorkompilierten Header zu verwenden: Stattdessen müssen Sie genau angeben, welche * .cpp-Datei sie generiert (d. H. "Stdafx.cpp") und wie viele der Header vorkompiliert werden. – ChrisW

0

es hängt davon ab, welche Sprache/Plattform, die Sie‘ Programmierung für. für die .NET-Entwicklung minimieren Sie die Anzahl der Projekte, die Sie in Ihrer Lösung haben.

0

In den alten Tagen konnten Sie dramatische Beschleunigungen bekommen, indem Sie ein RAM-Laufwerk einrichten und dort kompilieren. Ich weiß nicht, ob das immer noch zutrifft.

+0

Ich erwähnte das auch in meiner Antwort. Ich bin auch neugierig, ob das noch jemand tut. –

1

Eiffel hatte eine Vorstellung von verschiedenen Zuständen eingefroren und neu zu kompilieren, dass die ganze Klasse neu kompiliert wurde nicht unbedingt bedeuten, verwenden verteilte Kompilierung könnte.

Wie viel können Sie die komplizierten Module aufteilen, und wie viel interessieren Sie, um sie zu verfolgen?

2

In den meisten Sprachen (ziemlich gut alles andere als C++) ist das Kompilieren einzelner Kompilierungseinheiten ziemlich schnell.

Binding/Linking ist oft, was langsam ist - der Linker muss das ganze Programm und nicht nur eine einzelne Einheit referenzieren.

C++ leidet als - sofern Sie nicht das pImpl-Idiom verwenden - es erfordert die Implementierungsdetails jedes Objekts und aller Inline-Funktionen, um Client-Code zu kompilieren.

Java (Quelle zu Bytecode) leidet, weil die Grammatik Objekte und Klassen nicht differenziert - Sie müssen die Foo-Klasse laden, um zu sehen, ob Foo.Bar.Baz das Baz-Feld des Objekts ist, auf das das statische Feld Balken von verweist die Foo-Klasse oder ein statisches Feld der Foo.Bar-Klasse. Sie können die Quelle der Foo-Klasse zwischen den beiden ändern und nicht die Quelle des Client-Codes ändern, sondern den Client-Code neu kompilieren, da der Bytecode zwischen den beiden Formen unterscheidet, obwohl die Syntax dies nicht tut . AFAIK Python Bytecode unterscheidet nicht zwischen den beiden - Module sind wahre Mitglieder ihrer Eltern.

C++ und C leiden, wenn Sie mehr Header als erforderlich enthalten, da der Präprozessor jeden Header mehrmals verarbeiten muss und der Compiler sie kompiliert. Das Minimieren der Header-Größe und -Komplexität hilft, was darauf hindeutet, dass eine bessere Modularität die Kompilierungszeit verbessern würde.Es ist nicht immer möglich, die Headerkompilierung zwischenzuspeichern, da die Definitionen, die vorhanden sind, wenn der Header vorverarbeitet wird, seine Semantik und sogar die Syntax ändern können.

C leidet, wenn Sie den Präprozessor viel verwenden, aber die eigentliche Kompilierung ist schnell; viel C-Code verwendet , um die Implementierung besser zu verstecken als C++ - ein C-Header kann leicht aus typedefs und Funktionsdeklarationen bestehen, was eine bessere Kapselung ergibt.

Also würde ich vorschlagen, Ihre Sprache ausblenden Implementierungsdetails aus Client-Code, und wenn Sie eine OO-Sprache mit beiden Instanz Mitglieder und Namespaces sind, machen Sie die Syntax für den Zugriff auf die zwei eindeutig. Ermöglichen Sie echte Module, sodass Client-Code nur die Schnittstelle und nicht die Implementierungsdetails berücksichtigen muss. Präprozessor-Makros oder andere Variationsmechanismen dürfen die Semantik referenzierter Module nicht ändern.

0

Eine einfache Sache: Stellen Sie sicher, dass der Compiler native Multi-Core-CPUs nutzen kann.

10

Ihr Hauptproblem ist heute I/O. Ihre CPU ist um ein Vielfaches schneller als der Hauptspeicher und der Arbeitsspeicher ist etwa 1000-mal schneller als der Zugriff auf die Festplatte.

Wenn Sie also keine umfangreichen Optimierungen am Quellcode vornehmen, wird die CPU die meiste Zeit darauf warten, dass Daten gelesen oder geschrieben werden.

Probieren Sie diese Regeln:

  1. Gestalten Sie Ihre Compiler in mehreren, unabhängigen Schritten zu arbeiten. Das Ziel besteht darin, jeden Schritt in einem anderen Thread ausführen zu können, sodass Sie Multi-Core-CPUs verwenden können. Es wird auch helfen, den gesamten Kompiliervorgang zu parallelisieren (d. H. Mehrere Dateien gleichzeitig zu kompilieren)

    Es ermöglicht Ihnen auch, viele Quelldateien im Voraus zu laden und vorzuverarbeiten, damit der eigentliche Kompilierungsschritt schneller ausgeführt werden kann.

  2. Versuchen Sie, Dateien unabhängig voneinander zu kompilieren. Erstellen Sie beispielsweise einen "fehlenden Symbolpool" für das Projekt. Fehlende Symbole sollten keine Kompilierungsfehler verursachen. Wenn Sie irgendwo ein fehlendes Symbol finden, entfernen Sie es aus dem Pool. Wenn alle Dateien kompiliert wurden, überprüfen Sie, ob der Pool leer ist.

  3. Erstellen Sie einen Cache mit wichtigen Informationen. Beispiel: Datei X verwendet Symbole aus Datei Y. Auf diese Weise können Sie die Kompilierung der Datei Z (die nichts in Y referenziert) überspringen, wenn sich Y ändert. Wenn Sie einen Schritt weiter gehen möchten, legen Sie alle Symbole, die irgendwo in einem Pool definiert sind. Wenn sich eine Datei so ändert, dass Symbole hinzugefügt/entfernt werden, wissen Sie sofort, welche Dateien betroffen sind (ohne sie zu öffnen).

  4. Kompilieren Sie im Hintergrund. Starten Sie einen Compiler-Prozess, der das Projektverzeichnis auf Änderungen prüft und kompiliert, sobald der Benutzer die Datei speichert. Auf diese Weise müssen Sie jedes Mal nur ein paar Dateien kompilieren, anstatt alles. Auf lange Sicht werden Sie viel mehr kompilieren, aber für den Benutzer werden Umsatzzeiten viel kürzer sein (= Zeit, die Benutzer warten müssen, bis sie das kompilierte Ergebnis nach einer Änderung ausführen kann).

  5. Verwenden Sie einen Just-in-Time-Compiler (z. B. kompilieren Sie eine Datei, wenn sie verwendet wird, z. B. in einer import-Anweisung). Die Projekte werden dann in Quellform verteilt und kompiliert, wenn sie zum ersten Mal ausgeführt werden. Python macht das. Um dies durchzuführen, können Sie die Bibliothek während der Installation Ihres Compilers vorkompilieren.

  6. Verwenden Sie keine Headerdateien. Bewahren Sie alle Informationen an einem einzigen Ort auf und generieren Sie gegebenenfalls Header-Dateien aus der Quelle.Vielleicht behalten Sie die Header-Dateien nur im Speicher und speichern sie nie auf der Festplatte.

+0

Das ist gut, aber Sie haben das Wichtigste weggelassen: Datei-I/O. –

+0

@ david.pfx: Datei-E/A ist ein gegebenes Problem. Durch die Einhaltung der Regeln wird ein Prozess erstellt, der am wenigsten von E/A abhängt. –

+0

Wie ich schon sagte, die Antwort hat viele gute Ideen, aber wenn Sie sich nicht explizit darauf konzentrieren, Wege zu finden, um die I/O zu beschleunigen, wird die Leistung nicht folgen. Um es anders auszudrücken: Wenn Ihr Compiler I/O-gebunden ist, macht das clevere Threading oder Caching keinen Unterschied. Ich denke, Ihre Antwort würde durch die Berücksichtigung dieses Punktes verbessert werden. –

1
  • Machen Sie die Grammatik einfach und eindeutig, und damit schnell und einfach zu analysieren.
  • Setzen Sie starke Einschränkungen für die Dateieinbindung.
  • Erlaube Compilierung ohne vollständige Informationen wann immer möglich (zB. Vorabkündigung in C und C++).
  • One-Pass-Kompilierung, wenn möglich.
0
  • Stellen Sie sicher, dass alles, was das erste Mal kompiliert werden können Sie versuchen, es zu kompilieren. Z.B. Vorwärtsverweise verbieten.
  • Verwenden Sie eine kontextfreie Grammatik, damit Sie den richtigen Syntaxbaum ohne eine Symboltabelle finden können.
  • Stellen Sie sicher, dass die Semantik aus der Syntax abgeleitet werden kann, so dass Sie die richtige AST direkt erstellen können, anstatt mit einem Parsebaum und einer Symboltabelle zu mucken.
0

Wie ernst ist ein Compiler das?

Wenn die Syntax nicht ziemlich verworren ist, sollte der Parser nicht mehr als 10-100 Mal langsamer als nur durch die Zeichen der Eingabedatei ausgeführt werden.

In ähnlicher Weise sollte die Codegenerierung durch Formatierung der Ausgabe begrenzt werden.

Sie sollten keine Leistungsprobleme haben, wenn Sie nicht einen großen, seriösen Compiler machen, der in der Lage ist, Mega-Line-Anwendungen mit vielen Header-Dateien zu verarbeiten.

Dann müssen Sie sich Gedanken über vorkompilierte Header, Optimierungsdurchläufe und Verknüpfungen machen.

3

Ich habe selbst einen Compiler implementiert, und musste es mir ansehen, nachdem die Leute angefangen hatten, Hunderte von Quelldateien zu füttern. Ich war ziemlich überrascht, was ich herausgefunden habe.

Es stellt sich heraus, dass das Wichtigste, was Sie optimieren können, nicht Ihre Grammatik ist. Es ist auch nicht Ihr lexikalischer Analysator oder Ihr Parser. Das Wichtigste in Bezug auf die Geschwindigkeit ist stattdessen der Code, der Ihre Quelldateien von der Festplatte einliest. E/A's auf die Festplatte sind langsam. Wirklich langsam. Sie können die Geschwindigkeit Ihres Compilers anhand der Anzahl der ausgeführten Festplatten-I/Os messen.

So stellt sich heraus, dass das Beste, was Sie tun können, um einen Compiler zu beschleunigen, die gesamte Datei in einem großen I/O in den Speicher zu lesen, alle Ihre lexing, Parsing usw. aus RAM und dann Schreiben Sie das Ergebnis in einem großen I/O auf die Festplatte.

Ich unterhielt mich mit einem der Leiter, die Gnat (Ada-Compiler von GCC) darüber belehren, und er sagte mir, dass er eigentlich alles auf RAM-Platten legte, so dass sogar seine Datei-I/O wirklich nur RAM war liest und schreibt.

+0

Ich dachte mit einem Named Pipe anstelle einer Datei und andere Möglichkeiten, es in Ram zu halten. Ich konnte mir keinen Weg vorstellen, um die Verknüpfung zu verbessern. Kann ich Sie dazu bringen, mir [email protected] zu mailen, habe ich Tonnen von Fragen für Sie. –

+0

Ich würde nicht durch herkulische Bemühungen gehen, bevor Sie die Möglichkeit haben, die Dinge tatsächlich zu messen. Vorzeitige Optimierung und was nicht. Stellen Sie sicher, dass das Front-End für den Lexer modular genug ist, um später durch eine Puffer-API ersetzt zu werden. –

+1

Absolut korrekt. In den meisten Sprachen dominiert die E/A-Zeit (Eingabe und Ausgabe) der Datei absolut alles, was Sie sonst noch tun. Der Lexer leistet auch einen Beitrag. Alles andere ist ein entferntes Drittel. –

1

Eine Sache überraschend in Antworten bis jetzt: Sie machen Sie eine kontextfreie Grammatik, etc. Haben Sie einen guten Blick auf Sprachen von Wirth entworfen wie Pascal & Modula-2. Sie müssen Pascal nicht erneut implementieren, aber das Grammatik-Design ist für schnelles Kompilieren maßgeschneidert. Dann sehen Sie, ob Sie irgendwelche alten Artikel über die Tricks finden können, die Anders beim Implementieren von Turbo Pascal machte. Tipp: Tisch gefahren.

0

Ich habe nicht viel Arbeit zur Minimierung der Kompilierzeit gesehen. Aber einige Ideen kommen in den Sinn:

  1. Halten Sie die Grammatik einfach. Convoluted Grammatik wird Ihre Kompilierzeit erhöhen.
  2. Versuchen Sie, Parallelität zu verwenden, entweder mit Multicore-GPU oder CPU.
  3. Benchmark einen modernen Compiler und sehen, was sind die Engpässe und was Sie in Ihrem Compiler/Sprache tun können, um sie zu vermeiden.

Sofern Sie eine hoch spezialisierte Sprache schreiben, ist Zeit kompiliert nicht wirklich ein Problem ..

0

ein Build-System machen, die nicht saugen!

Es gibt eine riesige Menge an Programmen mit vielleicht 3 Quelldateien, die weniger als eine Sekunde dauern, um zu kompilieren, aber bevor Sie so weit kommen, müssen Sie ein Automakeskript durchsehen, das ungefähr 2 Minuten Dinge wie die Größe eines int. Und wenn Sie eine Minute später etwas anderes kompilieren, werden Sie fast genau die gleichen Tests bestehen.

Also, wenn Ihr Compiler ist den Benutzer schreckliche Dinge zu tun, die Größe seines int s wie das Ändern oder Grundfunktion Implementierungen zwischen den Läufen zu ändern, Dump nur, dass Informationen in eine Datei und lassen Sie sie es in einem zweiten bekommen statt von 2 Minuten.

2

Hier sind einige Performance-Tricks, die wir durch die Messung Übersetzungsgeschwindigkeit gelernt haben und was er betrifft:

  • schreiben Two-Pass-Compiler: Zeichen zu IR, IR-Code. (Es ist einfacher, ein drei -pass Compiler zu schreiben, die Zeichen geht -> AST -> IR -> Code, aber es ist nicht so schnell.)

  • Als logische Folge keinen Optimierer haben; Es ist schwer, einen schnellen Optimierer zu schreiben.

  • Erwägen Sie, Bytecode anstelle von systemeigenem Maschinencode zu generieren. Die virtuelle Maschine für ist ein gutes Modell.

  • Versuchen Sie einen Linear-Scan-Registerzuordner oder den einfachen Registerzuordner, den Fraser und Hanson in lcc verwendet haben.

  • In einem einfachen Compiler ist die lexikalische Analyse oft der größte Leistungsengpass. Wenn Sie C- oder C++ - Code schreiben, verwenden Sie re2c. Wenn Sie eine andere Sprache verwenden (die Sie sehr viel angenehmer finden), lesen Sie den Artikel über re2c und wenden Sie die gelernten Lektionen an.

  • Code mit maximalem Munch erzeugen, oder möglicherweise iburg.

  • Überraschenderweise ist der GNU-Assembler bei vielen Compilern ein Flaschenhals. Wenn Sie Binär direkt generieren können, tun Sie dies. Oder sehen Sie sich die New Jersey Machine-Code Toolkit an.

  • Wie oben erwähnt, entwerfen Sie Ihre Sprache, um etwas wie #include zu vermeiden. Verwenden Sie entweder keine Schnittstellendateien oder kompilieren Sie Ihre Schnittstellendateien vorkompilieren.Diese Taktik reduziert drastisch die Belastung des Lexers, was, wie gesagt, oft der größte Engpass ist.

+0

IR bedeutet was? Ich schaue jetzt diese Informationen auf, ist nicht lexikalische Analyse kein Problem mehr? Ich dachte, IO wäre das Hauptproblem (wie in Ihrer Analyse zu vielen Dateien gelesen, nicht die tatsächliche Komplexität). Meine Sprache ich plane, komplex zu sein. –

+0

IR bedeutet Zwischendarstellung. In einem einfachen Compiler ist das Lexen immer noch teuer; Die Schleife berührt jedes Zeichen des Eingangs. Sie möchten * beide * die gesamte Eingabegröße niedrig halten * und *, um Lexing effizient zu machen. Eine komplexe Grammatik ist in Ordnung. –

Verwandte Themen