Wie wir 10 Millionen Zeilen C++-Code auf den Standard C++14 (und dann auf C++17) ĂŒbertragen haben

Vor einiger Zeit (Herbst 2016) stellte sich im Entwicklungsteam bei der Entwicklung einer neuen Version der technologischen Plattform 1C:Unternehmen die Frage nach der UnterstĂŒtzung des neuen Standards C++14 in unserem Code. Der Umstieg auf den neuen Standard wĂŒrde, wie wir annahmen, viele Dinge eleganter, einfacher und zuverlĂ€ssiger machen und die Wartung und Pflege des Codes erleichtern. Und in der Übersetzung scheint es nichts Außergewöhnliches zu geben, wenn man nicht die GrĂ¶ĂŸenordnung der Codebasis und die spezifischen Eigenschaften unseres Codes berĂŒcksichtigt.

FĂŒr diejenigen, die es nicht wissen: 1C:Unternehmen ist eine Umgebung fĂŒr die schnelle Entwicklung von plattformĂŒbergreifenden GeschĂ€ftsanwendungen und eine Runtime fĂŒr deren AusfĂŒhrung in verschiedenen Betriebssystemen und Datenbankmanagementsystemen. Im Allgemeinen umfasst das Produkt:

Wir versuchen, den grĂ¶ĂŸtmöglichen Teil des Codes fĂŒr verschiedene Betriebssysteme zu schreiben — die Codebasis des Servers betrĂ€gt 99%, die des Clients etwa 95%. Die technologische Plattform 1C:Unternehmen ist ĂŒberwiegend in C++ geschrieben, und hier sind die ungefĂ€hren Merkmale des Codes:

  • 10 Millionen Zeilen C++-Code,
  • 14.000 Dateien,
  • 60.000 Klassen,
  • eine halbe Million Methoden.

Und all das musste auf C++14 umgestellt werden. Heute werden wir darĂŒber berichten, wie wir das gemacht haben und mit welchen Herausforderungen wir dabei konfrontiert wurden.

Wie wir 10 Millionen Zeilen C++-Code auf den Standard C++14 (und dann auf C++17) ĂŒbertragen haben

Haftungsausschluss

Alles, was unten ĂŒber die langsame/schnelle Leistung, den (nicht) geringen Speicherverbrauch von Implementierungen der Standardklassen in verschiedenen Bibliotheken geschrieben ist, bedeutet eines: Es gilt FÜR UNS. Es ist durchaus möglich, dass die Standardimplementierungen fĂŒr Ihre Aufgaben am besten geeignet sind. Wir haben uns jedoch an unseren Aufgaben orientiert: Wir haben typische Daten unserer Kunden genommen, typische Szenarien damit durchlaufen, die Leistung, den Speicherverbrauch usw. betrachtet und analysiert, ob uns und unseren Kunden solche Ergebnisse akzeptabel sind oder nicht. Und wir haben entsprechend gehandelt.

Was wir hatten

UrsprĂŒnglich schrieben wir den Code der Plattform 1C:Enterprise 8 in Microsoft Visual Studio. Das Projekt begann Anfang der 2000er Jahre und wir hatten eine Version nur fĂŒr Windows. NatĂŒrlich hat sich der Code seitdem aktiv entwickelt, viele Mechanismen wurden vollstĂ€ndig ĂŒberarbeitet. Aber der Code wurde nach dem Standard von 1998 geschrieben, und beispielsweise waren die rechten spitzen Klammern bei uns durch Leerzeichen getrennt, damit die Kompilierung erfolgreich verlief, so:

vector<vector > IntV;

Im Jahr 2006, mit der Veröffentlichung der Version 8.1 der Plattform, begannen wir, Linux zu unterstĂŒtzen und wechselten zu einer externen Standardbibliothek STLPort. Einer der GrĂŒnde fĂŒr den Wechsel war die Arbeit mit breiten Zeichenfolgen. In unserem Code verwenden wir ĂŒberall std::wstring, basierend auf dem Typ wchar_t. Seine GrĂ¶ĂŸe betrĂ€gt in Windows 2 Bytes, in Linux standardmĂ€ĂŸig 4 Bytes. Dies fĂŒhrte zu InkompatibilitĂ€t unserer binĂ€ren Protokolle zwischen Client und Server sowie zu verschiedenen persistenten Daten. Mit gcc-Optionen kann man angeben, dass die GrĂ¶ĂŸe von wchar_t bei der Kompilierung ebenfalls 2 Bytes betragen soll, aber dann kann man die Verwendung der Standardbibliothek des Compilers vergessen, da sie glibc verwendet und diese wiederum fĂŒr 4-Byte-wchar_t kompiliert ist. Weitere GrĂŒnde waren eine qualitativ hochwertigere Implementierung der Standardklassen, UnterstĂŒtzung von Hash-Tabellen und sogar die Emulation der Verschiebungssemantik innerhalb von Containern, die wir intensiv genutzt haben. Und ein weiterer Grund, wie man sagen könnte last but not least, war die Leistung von Zeichenfolgen. Wir hatten unsere eigene Klasse fĂŒr Zeichenfolgen, da in Anbetracht der Spezifik unseres Softwares Zeichenoperationen sehr weit verbreitet sind und fĂŒr uns kritische Bedeutung haben.

Unsere Zeichenfolge basiert auf den Ideen zur Optimierung von Zeichenfolgen, die bereits Anfang der 2000er Jahre geĂ€ußert wurden von Andrei Alexandrescu. SpĂ€ter, als Alexandrescu bei Facebook arbeitete, wurde auf seine Anregung hin eine Zeichenkette im Facebook-Engine verwendet, die nach Ă€hnlichen Prinzipien funktionierte (siehe Bibliothek folly).

. In unserer Zeichenkette kamen zwei Hauptoptimierungstechnologien zum Einsatz:

  1. FĂŒr kurze Werte wird ein interner Puffer im Zeichenkettenobjekt verwendet (der keine zusĂ€tzliche Speicheralokation benötigt).
  2. FĂŒr alle anderen kommt die Mechanik Copy On Write. Der Wert der Zeichenkette wird an einem Ort gespeichert, bei Zuweisung/Änderung wird ein ReferenzzĂ€hler verwendet.

Um die Kompilierungszeit der Plattform zu beschleunigen, haben wir die Stream-Implementierung aus unserer Version von STLPort ausgeschlossen (die wir nicht verwendet haben), was uns eine etwa 20%ige VerkĂŒrzung der Kompilierungszeit brachte. SpĂ€ter mussten wir eingeschrĂ€nkt verwenden In den nĂ€chsten zwei Monaten wird die genehmigte Spezifikation in der Phase der Dokumentenvorbereitung zur Veröffentlichung sein, in der an der redaktionellen Bearbeitung von Rechtschreibfehlern und Druckfehlern gearbeitet wird. Anfang November wird die resultierende Version des Dokuments zur Veröffentlichung an die ISO unter dem formalen Namen ISO/IEC 14882:2020 gesendet. In der Zwischenzeit hat der Ausschuss bereits mit der Arbeit am nĂ€chsten Standard C++23 (C++2b) begonnen und wird beim nĂ€chsten virtuellen Treffen mögliche. Boost nutzt aktiv Streams, insbesondere in seinen Service-APIs (zum Beispiel zum Logging), weshalb wir es modifizieren mussten, indem wir die Nutzung von Streams ausschlossen. Das erschwerte uns den Umstieg auf neue Boost-Versionen.

Der dritte Weg

Bei der Umstellung auf den C++14-Standard haben wir folgende Optionen in Betracht gezogen:

  1. Die von uns modifizierte STLPort auf den C++14-Standard zu portieren. Diese Option ist sehr kompliziert, da die UnterstĂŒtzung von STLPort 2010 eingestellt wurde und wir den gesamten Code selbst portieren mĂŒssten.
  2. Wechsel zu einer anderen STL-Implementierung, die mit C++14 kompatibel ist. Es ist wĂŒnschenswert, dass diese Implementierung unter Windows und Linux lĂ€uft.
  3. Bei der Kompilierung jede OS einen im entsprechenden Compiler integrierten Bibliothek zu verwenden.

Die erste Option wurde sofort aufgrund des zu großen Arbeitsaufwands abgelehnt.

Wir dachten eine Zeit lang ĂŒber die zweite Option nach; als Kandidaten betrachteten wir libc++, aber zu diesem Zeitpunkt lief es nicht unter Windows. Um libc++ unter Windows zu portieren, mĂŒssten wir viel Arbeit leisten — zum Beispiel alles, was mit Threads, Thread-Synchronisation und AtomicitĂ€t zu tun hat, selbst zu schreiben, da in libc++ in diesen Bereichen POSIX API.

verwendet wurde, und wir wÀhlten den dritten Weg.

Übergang

Wir mussten also die Nutzung 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).

Zum GlĂŒck wurde unser Code hauptsĂ€chlich nach den Richtlinien geschrieben und verwendete keine ausgeklĂŒgelten Tricks, sodass die Migration auf die neuen Bibliotheken vergleichsweise reibungslos verlief, unterstĂŒtzt durch Skripte, die in den Quelldateien die Namen von Typen, Klassen, NamensrĂ€umen und Includes ersetzten. Die Migration betraf 10.000 Quelldateien (von 14.000). wchar_t wurde durch char16_t ersetzt; wir haben uns entschieden, auf die Verwendung von wchar_t zu verzichten, da char16_t auf allen Betriebssystemen 2 Byte einnimmt und die KompatibilitĂ€t des Codes zwischen Windows und Linux nicht beeintrĂ€chtigt.

Es gab jedoch einige kleine Abenteuer. Zum Beispiel konnte im STLPort-Iterator implizit auf einen Zeiger auf ein Element gecastet werden, was an einigen Stellen in unserem Code verwendet wurde. In den neuen Bibliotheken war das nicht mehr möglich, und diese Stellen mussten manuell analysiert und umgeschrieben werden.

So, die Migration des Codes ist abgeschlossen, der Code wird fĂŒr alle Betriebssysteme kompiliert. Es ist Zeit fĂŒr die Tests.

Die Tests nach der Migration zeigten einen Leistungsabfall (stellenweise um 20-30%) und einen Anstieg des Speicherverbrauchs (um 10-15%) im Vergleich zur alten Version des Codes. Dies war unter anderem auf die suboptimale Handhabung von Standardstrings zurĂŒckzufĂŒhren. Deshalb mussten wir erneut unsere eigene, leicht ĂŒberarbeitete String-Implementierung verwenden.

Außerdem stellte sich eine interessante Eigenschaft der Container-Implementierungen in den eingebetteten Bibliotheken heraus: Leere (ohne Elemente) std::map und std::set aus den eingebetteten Bibliotheken allozieren Speicher. In unserem Code wurden aufgrund der Implementierungsmerkmale an einigen Stellen viele leere Container dieses Typs erstellt. Die Standardcontainer allozieren wenig Speicher, nur fĂŒr ein Wurzel-Element, aber fĂŒr uns war das kritisch – in einigen Szenarien fiel die Leistung spĂŒrbar ab und der Speicherverbrauch stieg (im Vergleich zu STLPort). Daher haben wir diese beiden Typen von Containern aus den eingebetteten Bibliotheken in unserem Code durch ihre Implementierung von Boost ersetzt, bei der diese Container nicht diese Besonderheit hatten, und das löste das Problem mit der Verlangsamung und dem erhöhten Speicherverbrauch.

Wie hĂ€ufig nach umfangreichen Änderungen in großen Projekten, hatte die erste Iteration des Quellcodes einige Probleme, und hier war insbesondere die UnterstĂŒtzung fĂŒr Debug-Iteratoren in der Windows-Implementierung sehr hilfreich. Schritt fĂŒr Schritt kamen wir voran, und bis zum FrĂŒhling 2017 (Version 8.3.11 1C:Enterprise) war die Migration abgeschlossen.

Ergebnisse

Der Übergang zu C++14 dauerte etwa 6 Monate. Den Großteil der Zeit arbeitete ein sehr hochqualifizierter Entwickler allein am Projekt, und in der finalen Phase kamen Vertreter der Teams hinzu, die fĂŒr bestimmte Bereiche zustĂ€ndig sind — UI, Cluster-Server, Entwicklungs- und Verwaltungstools usw.

Der Übergang hat unsere Arbeit an der Migration zu den neuesten Versionen des Standards erheblich erleichtert. So wurde die Version 1C:Unternehmen 8.3.14 (in Entwicklung, die Veröffentlichung ist fĂŒr Anfang nĂ€chsten Jahres geplant) bereits auf den Standard C++17.

Nach der Migration hatten die Entwickler mehr Möglichkeiten. FrĂŒher hatten wir unsere angepasste Version der STL und einen Namensraum std, jetzt befinden sich im Namensraum std die Standardklassen aus den Standardbibliotheken des Compilers, im Namensraum stdx – unsere, fĂŒr unsere Aufgaben optimierten Strings und Container, und in Boost – die neueste Version von Boost. Der Entwickler verwendet die Klassen, die optimal fĂŒr die Lösung seiner Aufgaben geeignet sind.

Auch die "native" Implementierung von Move-Konstruktoren (move constructors) fĂŒr eine Reihe von Klassen hilft bei der Entwicklung. Wenn eine Klasse einen Move-Konstruktor hat und diese Klasse in einen Container eingefĂŒgt wird, optimiert die STL das Kopieren der Elemente innerhalb des Containers (zum Beispiel, wenn der Container erweitert wird und die KapazitĂ€t verĂ€ndert und der Speicher neu zugewiesen werden muss).

Ein Wermutstropfen

Das vielleicht unangenehmste (aber nicht kritische) Ergebnis der Migration — wir sind auf ein erhöhtes Volumen gestoßen obj-Dateien, und das vollstĂ€ndige Build-Ergebnis mit allen Zwischenfiles benötigt nun 60 – 70 GB. Dieses Verhalten hĂ€ngt mit den Eigenschaften moderner Standardbibliotheken zusammen, die weniger kritisch mit dem Volumen der generierten Hilfsdateien umgehen. Dies hat keinen Einfluss auf die Funktion des kompilierten Programms, verursacht jedoch einige Unannehmlichkeiten in der Entwicklung, insbesondere erhöht es die Kompilierungszeit. Auch die Anforderungen an den freien Speicherplatz auf den Build-Servern und den Maschinen der Entwickler steigen. Unsere Entwickler arbeiten parallel an mehreren Versionen der Plattform, und hunderte Gigabyte an Zwischenfiles schaffen manchmal Schwierigkeiten in der Arbeit. Das Problem ist unangenehm, aber nicht kritisch, eine Lösung haben wir vorerst aufgeschoben. Eine der Lösungen, die wir in Betracht ziehen, ist die Technik unity build (sie wird unter anderem von Google bei der Entwicklung des Browsers Chrome verwendet).

Quelle: habr.com

ZuverlĂ€ssiges Hosting fĂŒr Websites mit DDoS-Schutz kaufen, VPS VDS Server đŸ”„ ZuverlĂ€ssiges Hosting fĂŒr Websites mit DDoS-Schutz kaufen, VPS VDS Server - ProHoster