Wie kann man mit dem Problem des (Kompilierens) einer großen Codebasis umgehen?

10

Obwohl ich programmieren kann, habe ich noch keine Erfahrung mit der Arbeit an großen Projekten. Bisher habe ich entweder kleine Programme codiert, die in Sekundenschnelle kompiliert werden (verschiedene c / c ++ - Übungen wie Algorithmen, Programmierprinzipien, Ideen, Paradigmen oder einfach nur APIs ausprobieren ...) oder an einigen kleineren Projekten gearbeitet Erstellt in einer oder mehreren Skriptsprachen (Python, PHP, JS), in denen keine Kompilierung erforderlich ist.

Die Sache ist, wenn ich in einer Skriptsprache codiere, wann immer ich versuchen möchte, ob etwas funktioniert - ich führe einfach das Skript aus und sehe, was passiert. Wenn die Dinge nicht funktionieren, kann ich einfach den Code ändern und ihn erneut ausprobieren, indem ich das Skript erneut ausführe und so lange mache, bis ich das gewünschte Ergebnis erhalte. Mein Punkt ist, dass Sie nicht warten müssen Alles, was kompiliert werden muss, ist einfach, eine große Codebasis zu erstellen, zu ändern, etwas hinzuzufügen oder einfach damit zu spielen - Sie können die Änderungen sofort sehen.

Als Beispiel nehme ich Wordpress. Es ist ziemlich einfach, herauszufinden, wie man ein Plugin dafür erstellt. Zuerst erstellen Sie ein einfaches "Hello World" -Plugin, dann erstellen Sie eine einfache Oberfläche für das Admin-Panel, um sich mit der API vertraut zu machen. Dann bauen Sie sie auf und erstellen etwas Komplexeres. In der Zwischenzeit ändern Sie das Aussehen einiger Plugins Die Idee, nach jeder kleinen Änderung immer wieder etwas so Großes wie WP neu kompilieren zu müssen, um zu versuchen, "ob es funktioniert" und "wie es funktioniert / sich anfühlt", scheint einfach ineffizient, langsam und falsch.

Wie könnte ich das mit einem Projekt machen, das in einer kompilierten Sprache geschrieben ist? Ich möchte zu einigen Open-Source-Projekten beitragen, und diese Frage nervt mich immer wieder. Die Situation ist wahrscheinlich von Projekt zu Projekt unterschiedlich, da einige von ihnen, die mit Bedacht vorausgesehen wurden, in gewisser Weise "modular" sind, während andere nur ein einziger großer Blob sind, der immer wieder neu kompiliert werden muss.

Ich würde gerne mehr darüber erfahren, wie das richtig gemacht wird. Was sind einige gängige Praktiken, Ansätze und Projektdesigns (Muster?), Um damit umzugehen? Wie heißt diese "Modularität" in der Welt der Programmierer und worauf sollte ich googeln, um mehr darüber zu erfahren? Wachsen Projekte oft aus ihren ersten Gedankenverhältnissen heraus, was nach einer Weile problematisch wird? Gibt es eine Möglichkeit, das lange Kompilieren von nicht so gut gestalteten Projekten zu vermeiden ? Eine Möglichkeit, sie irgendwie zu modularisieren (möglicherweise nicht wichtige Teile des Programms während der Entwicklung auszuschließen (irgendwelche anderen Ideen?))?

Vielen Dank.

pootzko
quelle
4
Ob. XKCD und das entsprechende thinkgeek T-Shirt * 8 ')
Mark Booth
1
Wenn Sie an einem ausreichend großen Projekt mit einem ausreichend großen Budget arbeiten, können Sie Build-Server veranlassen, die Kompilierung für Sie
durchzuführen
@Chad - Ich weiß das, aber es ist nur meine Heim-Gnu / Linux-Desktop-Maschine und ich im Moment :)
Pootzko
@Chad Ok, Sie sagen uns also, wir brauchen dedizierte Server , um mit Java (oder einer anderen kompilierten Sprache) fertig zu werden? Das ist totaler Mist
Kolob Canyon
1
@KolobCanyon - Nein, ich sage, es gibt eine Skala, an der Sie arbeiten könnten, für die sie erforderlich wären. und dass sie jetzt billig genug sind, da eine On-Demand-VM für das schnelle Kompilieren und Automatisieren von Tests so einfach ist, dass der Umfang nicht so groß ist.
SoylentGray

Antworten:

8

Wie bereits gesagt, kompilieren Sie das gesamte Projekt nie jedes Mal neu, wenn Sie eine kleine Änderung vornehmen. Stattdessen kompilieren Sie nur den Teil des Codes, der sich geändert hat, sowie den gesamten Code, der davon abhängt.

In C / C ++ ist das Kompilieren ziemlich einfach. Sie kompilieren jede Quelldatei in Maschinencode (wir nennen sie Objektdateien * .o) und verknüpfen dann alle Ihre Objektdateien zu einer großen ausführbaren Datei.

Wie bereits von MainMa erwähnt, sind einige Bibliotheken in separate Dateien integriert, die zur Laufzeit dynamisch mit der ausführbaren Datei verknüpft werden. Diese Bibliotheken werden unter Unix als Shared Objects (* .so) und unter Windows als Dynamically Linked Libraries (DLL) bezeichnet. Dynamische Bibliotheken haben viele Vorteile. Einer davon ist, dass Sie sie nicht kompilieren / verknüpfen müssen, es sei denn, ihr Quellcode ändert sich effektiv.

Es gibt Tools zur Build-Automatisierung, die Ihnen helfen:

  • Geben Sie Abhängigkeiten zwischen verschiedenen Teilen Ihres Quellbaums an.
  • Starten Sie pünktliche, diskrete Kompilierungen nur in dem Teil, der geändert wurde.

Die bekanntesten (make, ant, maven, ...) können automatisch erkennen, welche Teile des Codes seit der letzten Kompilierung geändert wurden und welches Objekt / welche Binärdatei genau aktualisiert werden muss.

Dies ist jedoch mit den (relativ geringen) Kosten für das Schreiben eines "Build-Skripts" verbunden. Es handelt sich um eine Datei, die alle Informationen zu Ihrem Build enthält, z. B. das Definieren der Ziele und ihrer Abhängigkeiten, das Definieren des gewünschten Compilers und der zu verwendenden Optionen, das Definieren Ihrer Build-Umgebung, Ihrer Bibliothekspfade usw. Sie haben möglicherweise von Makefiles gehört (sehr häufig in der Unix-Welt) oder build.xml (sehr beliebt in der Java-Welt). Das machen sie.

Rahmu
quelle
2
Ant (Java) kann nicht feststellen, was neu kompiliert werden muss. Es behandelt den trivialen Teil des Jobs und kompiliert den geänderten Quellcode neu, versteht jedoch Klassenabhängigkeiten überhaupt nicht. Wir verlassen uns dabei auf IDEs, und sie gehen schief, wenn eine Methodensignatur so geändert wird, dass keine Änderung des aufrufenden Codes erforderlich ist.
Kevin Cline
@kevincline Ich stimme dem zu - ANT kompiliert alles, es sei denn, Sie geben etwas anderes in der build.xmlDatei an
Kolob Canyon
7

Sie kompilieren nicht jedes Mal das gesamte Projekt neu. Wenn es sich beispielsweise um eine C / C ++ - Anwendung handelt, besteht die Möglichkeit, dass sie in Bibliotheken (DLLs in Windows) aufgeteilt wird, wobei jede Bibliothek separat kompiliert wird.

Das Projekt selbst wird in der Regel täglich auf einem dedizierten Server kompiliert: Dies sind nächtliche Builds. Dieser Prozess kann viel Zeit in Anspruch nehmen, da er nicht nur die Kompilierungszeit, sondern auch die Zeit für die Ausführung von Komponententests, anderen Tests und anderen Prozessen umfasst.

Arseni Mourzenko
quelle
3
Wenn ich nicht alles neu kompiliere, wann habe ich dann Zeit, um mit meinem Trebuchet
SoylentGray
5

Ich denke, was alle Antworten bisher auch angedeutet haben, ist, dass große Softwareprojekte fast immer in viel kleinere Teile zerlegt werden. Jedes Stück wird normalerweise in einer eigenen Datei gespeichert.

Diese Stücke werden einzeln zusammengestellt , um Objekte zu erstellen. Die Objekte werden dann miteinander verbunden, um das Endprodukt zu bilden. [In gewisser Weise ist es so, als würde man Sachen aus Legos bauen. Du versuchst nicht, das Letzte aus einem großen Stück Plastik zu formen, sondern kombinierst ein paar kleinere Stücke, um es zu machen.]

Wenn Sie das Projekt in Teile zerlegen, die einzeln zusammengestellt werden, können einige nette Dinge passieren.

Inkrementelles Gebäude

Wenn Sie ein Teil wechseln, müssen Sie normalerweise nicht alle Teile neu kompilieren. Im Allgemeinen müssen die anderen nicht neu kompiliert werden, solange Sie nicht ändern, wie andere Teile mit Ihrem Teil interagieren.

Daraus ergibt sich die Idee des inkrementellen Bauens . Bei einem inkrementellen Build werden nur die Teile neu kompiliert, die von der Änderung betroffen waren. Dies beschleunigt die Entwicklungszeit erheblich. Zwar müssen Sie möglicherweise noch warten, bis alles neu verknüpft ist, aber es ist immer noch eine Ersparnis, wenn Sie alles neu kompilieren und neu verknüpfen müssen. (Übrigens: Einige Systeme / Sprachen unterstützen inkrementelle Verknüpfungen, sodass nur die Änderungen erneut verknüpft werden müssen. Die Kosten hierfür liegen normalerweise in einer schlechten Codeleistung und -größe.)

Unit Testing

Das zweite, was Sie mit kleinen Stücken tun können, ist, die Teile einzeln zu testen, bevor sie kombiniert werden. Dies wird als Unit Testing bezeichnet . Beim Komponententest wird jede Einheit einzeln getestet, bevor sie in den Rest des Systems integriert (kombiniert) wird. Unit-Tests werden normalerweise so geschrieben, dass sie schnell ausgeführt werden können, ohne den Rest des Systems einzubeziehen.

Der Grenzfall bei der Anwendung von Tests ist in Test Driven Development (TDD) zu sehen. In diesem Entwicklungsmodell wird kein Code geschrieben / geändert, es sei denn, es soll ein fehlgeschlagener Test behoben werden.

Einfacher machen

Das Aufteilen von Dingen scheint also gut zu sein, aber es scheint auch, dass viel Arbeit erforderlich ist, um das Projekt zu erstellen: Sie müssen herausfinden, welche Teile sich geändert haben und was von diesen Teilen abhängt, jedes Teil zusammenstellen und dann alles miteinander verknüpfen.

Glücklicherweise sind Programmierer faul * und erfinden viele Tools, um ihre Arbeit zu erleichtern. Zu diesem Zweck wurden viele Tools geschrieben, um die obige Aufgabe zu automatisieren. Die bekanntesten davon wurden bereits erwähnt (make, ant, maven). Mit diesen Tools können Sie definieren, welche Teile für Ihr endgültiges Projekt zusammengesetzt werden müssen und wie die Teile voneinander abhängen (dh wenn Sie dies ändern, muss dies neu kompiliert werden). Das Ergebnis ist, dass durch die Ausgabe nur eines Befehls herausgefunden wird, was neu kompiliert werden muss, kompiliert wird und alles neu verknüpft wird.

Aber das lässt immer noch herauszufinden, wie sich die Dinge zueinander verhalten. Das ist viel Arbeit und wie ich bereits sagte, sind Programmierer faul. Sie haben sich also eine andere Klasse von Werkzeugen ausgedacht. Diese Tools wurden geschrieben, um die Abhängigkeiten für Sie zu bestimmen! Oft sind die Tools Teil von Integrated Development Environments (IDEs) wie Eclipse und Visual Studio, aber es gibt auch einige eigenständige Tools, die sowohl für generische als auch für spezifische Anwendungen verwendet werden (makedep, QMake for Qt-Programme).

* Eigentlich sind Programmierer nicht wirklich faul, sie verbringen ihre Zeit nur gerne mit der Arbeit an Problemen und erledigen keine sich wiederholenden Aufgaben, die von einem Programm automatisiert werden können.

jwernerny
quelle
5

Hier ist meine Liste von Dingen, mit denen Sie versuchen können, C / C ++ - Builds zu beschleunigen:

  • Sind Sie so eingerichtet, dass nur das neu erstellt wird, was sich geändert hat? Die meisten Umgebungen tun dies standardmäßig. Es ist nicht erforderlich, eine Datei neu zu kompilieren, wenn sie oder keiner der Header geändert wurde. Ebenso gibt es keinen Grund, eine DLL / Exe neu zu erstellen, wenn sich nicht alle in objs / lib verknüpften geändert haben.
  • Fügen Sie Inhalte von Drittanbietern, die sich nie ändern, und die zugehörigen Header in einen schreibgeschützten Codebibliotheksbereich ein. Sie benötigen nur die Header und die zugehörigen Binärdateien. Sie sollten dies niemals aus einer anderen Quelle als vielleicht einmal neu erstellen müssen.
  • Bei der Neuerstellung waren die beiden einschränkenden Faktoren meiner Erfahrung nach die Anzahl der Kerne und die Festplattengeschwindigkeit . Holen Sie sich eine bullige Quad-Core-Maschine mit Hyperthread und einer wirklich guten Festplatte, und Ihre Leistung wird sich verbessern. Betrachten Sie ein Solid-State-Laufwerk - denken Sie daran, dass die billigen möglicherweise schlechter sind als eine gute Festplatte. Erwägen Sie die Verwendung von RAID, um Ihre Festplatte zu erhöhen
  • Verwenden Sie ein verteiltes Build-System wie Incredibuild, das die Kompilierung auf andere Arbeitsstationen in Ihrem Netzwerk aufteilt. (Stellen Sie sicher, dass Sie ein solides Netzwerk haben).
  • Richten Sie einen Unity-Build ein, um zu verhindern, dass Header-Dateien ständig neu geladen werden.
Doug T.
quelle
Nach meiner Erfahrung (nicht viel, aber gut) wird die Festplattengeschwindigkeit irrelevant, wenn Ihr Projekt über "sehr klein" hinausgeht. Denken Sie nur an das, was Sie in Ihrem nächsten Punkt sagen: Sie verwenden das Netzwerk, um die Kompilierung zu beschleunigen. Wenn die Festplatte ein großer Engpass war, scheint es kein guter Schritt zu sein, auf das Netzwerk zurückzugreifen.
R. Martinho Fernandes
Eine andere billige Lösung ist das Kompilieren in einem tmpfs. Kann die Leistung erheblich steigern, wenn der Kompilierungsprozess an E / A gebunden ist.
Artefakt2
4

Die Idee, nach jeder kleinen Änderung immer wieder etwas so Großes wie WP neu kompilieren zu müssen, um zu versuchen, "ob es funktioniert" und "wie es funktioniert / sich anfühlt", scheint einfach ineffizient, langsam und falsch.

Etwas Interpretiertes auszuführen ist auch sehr ineffizient und langsam und (wohl) falsch. Sie beschweren sich über die Zeitanforderungen auf dem PC des Entwicklers, aber das Nichtkompilieren verursacht Zeitanforderungen auf dem PC des Benutzers , was wahrscheinlich viel schlimmer ist.

Noch wichtiger ist, dass moderne Systeme recht fortgeschrittene inkrementelle Neuerstellungen durchführen können und es nicht üblich ist, das Ganze für geringfügige Änderungen neu zu kompilieren. Kompilierte Systeme können Skriptkomponenten enthalten, insbesondere für Dinge wie die Benutzeroberfläche.

DeadMG
quelle
1
Ich glaube, meine Frage war nicht dazu gedacht, interpretiert zu werden oder eine Debatte über den Ansatz zu führen. Stattdessen habe ich nur um Rat gefragt, wie die Entwicklung eines großen (kompilierten) Projekts richtig gemacht wird. Vielen Dank für die Idee des inkrementellen Umbaus.
Pootzko
@pootzko: Nun, es ist ziemlich unfair, die Nachteile des Kompilierens zu diskutieren, wenn Sie nicht auch über die Nachteile des Dolmetschens sprechen.
DeadMG
1
Nein, ist es nicht. Es ist eine weitere Debatte und hat nichts mit meiner Frage zu tun. Ich sage nicht, dass es etwas ist, das nicht diskutiert werden sollte. es sollte, aber nicht hier.
Pootzko
@pootzko: Dann sollten Sie den größten Teil Ihrer Frage nicht der Aufzählung widmen, was Sie am Kompilieren nicht mögen. Sie hätten etwas viel kürzeres und prägnanteres schreiben sollen, wie "Wie können die Kompilierungszeiten großer Projekte reduziert werden?".
DeadMG
Ich wusste nicht, dass ich jemanden fragen musste, wie ich meine Frage stellen sollte. : OIch ​​habe es so geschrieben, wie ich es getan habe, um meinen Standpunkt besser zu erklären, damit andere es besser verstehen und mir erklären können, wie man mit kompilierten Sprachen dasselbe / Ähnliches erreicht. Ich habe erneut - nicht - jemanden gebeten, mir zu sagen, ob interpretierte Sprachen zu schlechteren Zeitanforderungen auf dem PC des Benutzers führen. Ich weiß das und es hat nichts mit meiner Frage zu tun - "Wie wird es mit kompilierten Sprachen gemacht", sorry. Andere Leute scheinen herausgefunden zu haben, was ich gefragt habe, also denke ich nicht, dass meine Frage nicht klar genug ist.
Pootzko
4
  • Teilwiederherstellung

Wenn das Projekt eine ordnungsgemäße DAG für die Kompilierungsabhängigkeit implementiert, können Sie nur die Objektdateien neu kompilieren, auf die sich Ihre Änderung auswirkt.

  • Mehrfacher Kompilierungsprozess

Unter der Annahme einer ordnungsgemäßen DAG für die Kompilierungsabhängigkeit können Sie mit mehreren Prozessen kompilieren. Ein Job pro Kern / CPU ist die Norm.

  • Ausführbare Tests

Sie können zum Testen mehrere ausführbare Dateien erstellen, die nur bestimmte Objektdateien verknüpfen.

Dietbuddha
quelle
2

Zusätzlich zur Antwort von MainMa haben wir gerade die Maschinen aktualisiert, an denen wir arbeiten. Einer der besten Einkäufe, die wir getätigt haben, war eine SSD, bei der Sie das gesamte Projekt neu kompilieren müssen.

Ein weiterer Vorschlag wäre, einen anderen Compiler auszuprobieren. Früher wechselten wir von Javas Compiler zu Jikes und jetzt verwenden wir den mit Eclipse gebündelten Compiler (ich weiß nicht, ob er einen Namen hat), der Multicore-Prozessoren besser nutzt.

Das Kompilieren unseres 37.000-Dateien-Projekts dauerte ungefähr 15 Minuten, bevor wir diese Änderungen vorgenommen haben. Nach Änderungen wurde es auf 2-3 Minuten gekürzt.

Natürlich ist es erwähnenswert, den Punkt von MainMa noch einmal zu erwähnen. Kompilieren Sie das gesamte Projekt nicht jedes Mal neu, wenn Sie eine Änderung sehen möchten.

RP.
quelle