Wie wir 10 Millionen Zeilen C ++ - Code in den C ++ 14-Standard (und später in C ++ 17) übersetzt haben

    Vor einiger Zeit (im Herbst 2016) stellte sich bei der Entwicklung der nächsten Version der 1C: Enterprise-Technologieplattform im Entwicklungsteam die Frage nach der Unterstützung des neuen C ++ 14- Standards in unserem Code. Der Übergang zum neuen Standard würde es uns, wie erwartet, erlauben, viele Dinge eleganter, einfacher und zuverlässiger zu schreiben und die Unterstützung und Wartung des Codes zu vereinfachen. Und die Übersetzung scheint nichts Außergewöhnliches zu sein, wenn nicht der Umfang der Codebasis und die spezifischen Merkmale unseres Codes.

    Für diejenigen, die es nicht wissen, ist 1C: Enterprise eine Umgebung für die schnelle Entwicklung plattformübergreifender Geschäftsanwendungen und Laufzeit für deren Ausführung in verschiedenen Betriebssystemen und DBMS. Im Allgemeinen umfasst das Produkt:

    • Cluster von Anwendungsservern unter Windows und Linux
    • Der Client , der mit dem Server über http (s) oder sein eigenes Binärprotokoll arbeitet, läuft unter Windows, Linux und macOS
    • Webclient , der in den Browsern Chrome, Internet Explorer, Microsoft Edge, Firefox und Safari (in JavaScript geschrieben) ausgeführt wird
    • Entwicklungsumgebung ( Konfigurator ), läuft unter Windows, Linux und macOS
    • Anwendungsserver- Verwaltungstools , laufen unter Windows, Linux und macOS
    • Mobile Client stellt eine Verbindung zum Server über http (s) her und funktioniert auf mobilen Geräten, auf denen Android, iOS und Windows ausgeführt werden
    • Mobile Plattform - ein Framework zum Erstellen von Offline-Mobilanwendungen mit Synchronisierungsfunktion für Android, iOS und Windows
    • Entwicklungsumgebung 1C: Enterprise Development Tools , geschrieben in Java
    • Interaction System Server

    Wir versuchen, maximal einen Code für verschiedene Betriebssysteme zu schreiben - die Server-Codebasis ist zu 99% üblich und der Client zu ca. 95%. Die 1C: Enterprise-Technologieplattform ist hauptsächlich in C ++ geschrieben. Die folgenden Eigenschaften sind ungefähre Eigenschaften des Codes:

    • 10 Millionen Zeilen C ++ - Code
    • 14 tausend Dateien
    • 60 tausend Klassen
    • eine halbe Million Methoden.

    Und all diese Ökonomie sollte auf C ++ 14 übertragen werden. Wir werden Ihnen heute sagen, wie wir das gemacht haben und was uns dabei begegnet ist.

    Bild

    Haftungsausschluss


    Alles, was unten über langsames / schnelles Arbeiten und (nicht) hoher Speicherverbrauch durch Implementierungen von Standardklassen in verschiedenen Bibliotheken geschrieben wird, bedeutet eines: Dies gilt für uns. Möglicherweise sind Standardimplementierungen für Ihre Aufgaben am besten geeignet. Wir wurden von unseren Aufgaben abgestoßen: Wir haben typische Daten für unsere Kunden übernommen, typische Szenarien darauf getrieben, die Geschwindigkeit, den Speicherbedarf usw. untersucht und analysiert, ob wir und unsere Kunden mit diesen Ergebnissen zufrieden sind oder nicht. Und handelte je nach.

    Was wir hatten


    Anfangs haben wir den Code für die 1C: Enterprise 8-Plattform in Microsoft Visual Studio geschrieben. Das Projekt begann in den frühen 2000er Jahren und wir hatten nur eine Version für Windows. Natürlich wurde seitdem der Code aktiv entwickelt, viele Mechanismen wurden komplett neu geschrieben. Der Code wurde jedoch gemäß dem Standard von 1998 geschrieben, und zum Beispiel wurden unsere rechtwinkligen Klammern durch Leerzeichen getrennt, damit die Kompilierung erfolgreich ist:
    vector<vector<int> > IntV;

    Im Jahr 2006 begannen wir mit der Veröffentlichung der Version 8.1 der Plattform mit der Unterstützung von Linux und wechselten zur STLPort- Standardbibliothek eines Drittanbieters .. Einer der Gründe für die Umstellung bestand in der Arbeit mit breiten Linien. In unserem Code verwenden wir überall std :: wstring, basierend auf dem Typ von wchar_t. In Windows beträgt die Größe 2 Byte, in Linux sind es standardmäßig 4 Byte. Dies führte zur Inkompatibilität unserer binären Protokolle zwischen Client und Server sowie zu verschiedenen persistenten Daten. Mit den gcc-Optionen können Sie angeben, dass die Größe von wchar_t beim Kompilieren ebenfalls 2 Byte betrug. Dann können Sie jedoch vergessen, die Standardbibliothek des Compilers zu verwenden. es verwendet glibc, und diese wiederum wird für 4-Byte-wchar_t kompiliert. Andere Gründe waren die bessere Implementierung von Standardklassen, die Unterstützung von Hashtabellen und sogar die Emulation der Bewegungssemantik in Containern, die wir aktiv verwendeten. Und ein anderer Grund, wie sie nicht zuletzt sagen, war die Saitenleistung. Wir hatten unsere eigene Klasse für Streicher, weil Aufgrund der Natur unserer Software werden Zeichenkettenoperationen sehr häufig verwendet, was für uns von entscheidender Bedeutung ist.

    Unsere Linie basiert auf den Ideen der Optimierung der Linien, die Andrei Alexandrescu Anfang der 2000er Jahre formuliert hatte . Später, als Alexandrescu auf Facebook arbeitete und mit seiner Facebook-Engine eingereicht wurde, wurde eine Zeichenfolge verwendet, die nach ähnlichen Prinzipien arbeitet (siehe die Folly- Bibliothek ).

    Unsere Linie verwendete zwei Hauptoptimierungstechnologien:

    1. Bei kurzen Werten wird im String-Objekt selbst ein interner Puffer verwendet (der keine zusätzliche Speicherzuordnung erfordert).
    2. Für alle anderen verwenden Sie den Mechaniker Copy On Write . Der Wert des Strings wird an einer Stelle gespeichert, beim Zuweisen / Ändern wird der Referenzzähler verwendet.

    Um die Erstellung der Plattform zu beschleunigen, haben wir die Implementierung des Streams (den wir nicht verwendet haben) von unserer STLPort-Version ausgeschlossen. Dies gab uns eine Erstellungsgeschwindigkeit von etwa 20%. Anschließend mussten wir Boost in begrenztem Umfang einsetzen . Boost verwendet den Stream aktiv, insbesondere in seinen Service-APIs (z. B. für die Protokollierung). Daher mussten wir ihn anpassen, ohne dass der Stream verwendet wurde. Dies machte es uns wiederum schwer, auf neue Boost-Versionen zu wechseln.

    Dritter Weg


    Beim Übergang zum Standard C ++ 14 haben wir folgende Optionen in Betracht gezogen:

    1. Erhöhen Sie unseren modifizierten STLPort auf den C ++ 14-Standard. Die Option ist sehr schwierig, weil Der STLPort-Support wurde 2010 eingestellt und wir müssten den gesamten Code selbst abrufen.
    2. Wechseln Sie zu einer anderen STL-Implementierung, die mit C ++ 14 kompatibel ist. Es ist äußerst wünschenswert, dass diese Implementierung unter Windows und Linux ausgeführt wird.
    3. Verwenden Sie beim Kompilieren für jedes Betriebssystem die in den entsprechenden Compiler integrierte Bibliothek.

    Die erste Option wurde wegen zu viel Arbeit sofort abgelehnt.

    Wir haben eine Weile über die zweite Möglichkeit nachgedacht. Sie betrachteten libc ++ als Kandidaten , aber zu dieser Zeit funktionierte es nicht unter Windows. Um Libc ++ nach Windows zu portieren, müssen Sie viel Arbeit erledigen - beispielsweise das Schreiben von alles, was mit Threads, Threadsynchronisierung und Atomizität zu tun hat, da die POSIX-API in libc ++ verwendet wurde .

    Und wir haben uns für den dritten Weg entschieden.

    Übergang


    Daher mussten wir die Verwendung von STLPort durch die Bibliotheken der entsprechenden Compiler ersetzen (Visual Studio 2015 für Windows, gcc 7 für Linux, clang 8 für macOS).

    Glücklicherweise wurde unser Code hauptsächlich auf Richtlinien geschrieben und verwendete nicht alle Arten von kniffligen Tricks. Daher erfolgte die Migration zu neuen Bibliotheken relativ reibungslos. Dabei wurden mithilfe von Skripts die Namen von Typen, Klassen, Neymspeysov und Inklusion in den Quelldateien ersetzt. Die Migration betraf 10.000 Quelldateien (von 14.000). wchar_t wurde durch char16_t ersetzt; Wir haben uns entschieden, wchar_t nicht mehr zu verwenden, weil char16_t benötigt auf allen Betriebssystemen 2 Byte und beeinträchtigt nicht die Code-Kompatibilität zwischen Windows und Linux.

    Nicht ohne ein kleines Abenteuer. In STLPort könnte beispielsweise ein Iterator implizit in einen Zeiger auf ein Element umgewandelt werden, und an einigen Stellen in unserem Code wurde dies verwendet. In den neuen Bibliotheken war dies nicht mehr möglich, und diese Orte mussten manuell analysiert und neu geschrieben werden.

    Damit ist die Codemigration abgeschlossen, der Code wird für alle Betriebssysteme kompiliert. Es ist Zeit zu testen.

    Tests nach der Umstellung zeigten eine Abnahme der Leistung (an einigen Stellen um 20-30%) und einen Anstieg des Speicherverbrauchs (bis zu 10-15%) im Vergleich zur alten Version des Codes. Dies war insbesondere auf die suboptimale Arbeit von Standardzeichenketten zurückzuführen. Daher mussten wir die Linie erneut verwenden, etwas modifiziert.

    Ein interessantes Merkmal der Containerimplementierung in eingebetteten Bibliotheken wurde ebenfalls enthüllt: Leeres (ohne Elemente) std :: map und std :: set aus den integrierten Bibliotheken weisen Speicher zu. Und aufgrund der Implementierungseigenschaften werden an einigen Stellen des Codes einige leere Container dieses Typs erstellt. Sie ordnen Standardspeichercontainern ein wenig für ein Root-Element zu, aber für uns stellte sich dies als kritisch heraus. In einer Reihe von Szenarien haben wir die Leistung erheblich verringert und der Speicherbedarf ist gestiegen (verglichen mit STLPort). Daher haben wir in unserem Code diese beiden Arten von Containern aus den integrierten Bibliotheken durch ihre Implementierung aus Boost ersetzt, wo diese Container nicht über eine solche Funktion verfügten. Dadurch wurde das Problem mit der Verlangsamung und dem erhöhten Speicherbedarf gelöst.

    Wie es häufig nach umfangreichen Änderungen in großen Projekten der Fall ist, funktionierte die erste Iteration des Quellcodes nicht ohne Probleme. Hier waren wir besonders hilfreich, um Debugging-Iteratoren in der Windows-Implementierung zu unterstützen. Schritt für Schritt gingen wir voran und im Frühjahr 2017 (Version 8.3.11 von 1C: Enterprise) war die Migration abgeschlossen.

    Ergebnisse


    Die Umstellung auf Standard C ++ 14 dauerte etwa 6 Monate. Die meiste Zeit arbeitete ein (jedoch hochqualifizierter) Entwickler an dem Projekt, und in der Endphase waren Vertreter der Teams, die für bestimmte Bereiche (UI, Server-Cluster, Entwicklungs- und Verwaltungstools usw.) verantwortlich waren, miteinander verbunden.

    Der Übergang hat unsere Arbeit zur Migration auf die neuesten Versionen des Standards erheblich vereinfacht. Die Version 1C: Enterprise 8.3.14 (in Entwicklung ist die Veröffentlichung für Anfang nächsten Jahres geplant) wurde daher bereits in den Standard C ++ 17 übersetzt .

    Nach der Migration haben Entwickler mehr Möglichkeiten. Wenn wir früher eine eigene modifizierte Version von STL und einen Namespace std hatten, haben wir jetzt Standardklassen aus den integrierten Compilerbibliotheken im std-Namespace, unsere stdx-optimierten Zeilen und Container in boost, unserer neuesten Version von boost. Und der Entwickler verwendet die Klassen, die sich am besten für die Lösung seiner Probleme eignen.

    Es hilft auch bei der Entwicklung der nativen Implementierung von Bewegungskonstruktoren für eine Reihe von Klassen. Wenn die Klasse über einen Verschiebungskonstruktor verfügt und diese Klasse in einem Container platziert ist, optimiert STL das Kopieren von Elementen innerhalb des Containers (z. B. wenn der Container erweitert wird und Sie die Kapazität ändern und Speicher zuweisen müssen).

    Fliegen Sie in der Salbe


    Vielleicht die unangenehmste (aber nicht kritische) Folge der Migration - wir sind mit einer Zunahme von OBJ-Dateien konfrontiertund das vollständige Ergebnis des Builds mit allen Zwischendateien beläuft sich auf jeweils 60 - 70 GB. Dieses Verhalten hängt mit den Funktionen moderner Standardbibliotheken zusammen, die die Größe der generierten Servicedateien immer weniger kritisch beeinflussen. Dies hat keine Auswirkungen auf den Betrieb der kompilierten Anwendung, bringt jedoch eine Reihe von Nachteilen bei der Entwicklung mit sich, insbesondere erhöht sich die Kompilierungszeit. Die Anforderungen an die Freigabe von Speicherplatz auf Build-Servern und Entwicklercomputern steigen ebenfalls. Unsere Entwickler arbeiten parallel an mehreren Versionen der Plattform, und Hunderte Gigabytes an Zwischendateien verursachen manchmal Schwierigkeiten bei ihrer Arbeit. Das Problem ist unangenehm, aber nicht kritisch. Wir haben seine Entscheidung vorerst verschoben. Als eine der Lösungen betrachten wir die Unity-Build- Technik (Es wird insbesondere von Google bei der Entwicklung des Chrome-Browsers verwendet).

    Jetzt auch beliebt: