Hoe we 10 miljoen regels C++-code hebben vertaald naar de C++14-standaard (en vervolgens naar C++17)

Enige tijd geleden (in het najaar van 2016), bij het ontwikkelen van de volgende versie van het 1C:Enterprise technologieplatform, rees binnen het ontwikkelteam de vraag over het ondersteunen van de nieuwe standaard C ++ 14 in onze code. De overgang naar de nieuwe standaard zou ons, zoals we aannamen, in staat stellen om veel dingen eleganter, eenvoudiger en betrouwbaarder te schrijven en de ondersteuning en het onderhoud van de code te vereenvoudigen. En er lijkt niets buitengewoons aan de vertaling te zijn, ware het niet dat de schaal van de codebasis en de specifieke kenmerken van onze code er niet waren.

Voor degenen die het niet weten: 1C:Enterprise is een omgeving voor snelle ontwikkeling van platformonafhankelijke bedrijfsapplicaties en runtime voor de uitvoering ervan in verschillende besturingssystemen en DBMS. In het algemeen omvat de samenstelling van het product:

  • Applicatieservercluster, werkt op Windows en Linux
  • klant, werkend met de server via http(s) of zijn eigen binaire protocol, werkt op Windows, Linux, macOS
  • web cliënt, dat werkt in Chrome, Internet Explorer, Microsoft Edge, Firefox, Safari-browsers (geschreven in JavaScript)
  • Ontwikkelomgeving (Configurator), werkt op Windows, Linux, macOS
  • Beheertools applicatieservers, werken op Windows, Linux, macOS
  • Mobiele klant, verbinding maken met de server via http(s), werkt op mobiele apparaten met Android, iOS, Windows
  • Mobiel platform - een raamwerk voor het maken van offline mobiele applicaties met de mogelijkheid om te synchroniseren, werkend op Android, iOS, Windows
  • Ontwikkelomgeving 1C: Hulpprogramma's voor bedrijfsontwikkeling, geschreven in Java
  • Server Interactie systemen

We proberen zoveel mogelijk dezelfde code voor verschillende besturingssystemen te schrijven - de servercodebasis is voor 99% gemeenschappelijk, de client - voor ongeveer 95%. Technologisch platform 1C:Enterprise is voornamelijk geschreven in C++ en hieronder zijn de geschatte kenmerken van de code:

  • 10 miljoen regels C++-code,
  • 14 duizend bestanden,
  • 60 duizend lessen,
  • een half miljoen methodes.

En al deze economie moest worden vertaald in C ++ 14. Over hoe we het deden en wat we tegenkwamen in het proces, vertellen we vandaag.

Hoe we 10 miljoen regels C++-code hebben vertaald naar de C++14-standaard (en vervolgens naar C++17)

Disclaimer

Alles wat hieronder geschreven wordt over langzaam/snel werken, (niet) groot geheugengebruik door implementaties van standaardklassen in diverse bibliotheken betekent één ding: dit geldt VOOR ONS. Het is heel goed mogelijk dat standaardimplementaties het beste bij uw taken passen. We gingen uit van onze taken: we namen de typische gegevens van onze klanten, voerden er typische scenario's op uit, keken naar de snelheid, de hoeveelheid verbruikt geheugen, enz., en analyseerden of wij en onze klanten tevreden waren met dergelijke resultaten of niet. En handelde afhankelijk van.

Wat we hadden

Aanvankelijk schreven we de code voor het 1C:Enterprise 8-platform met behulp van Microsoft Visual Studio. Het project begon in de vroege jaren 2000 en we hadden een alleen-Windows-versie. Uiteraard is de code sindsdien actief ontwikkeld, veel mechanismen zijn volledig herschreven. Maar de code is geschreven volgens de standaard van 1998 en we hadden bijvoorbeeld door spaties gescheiden rechte punthaken om succesvol te kunnen compileren, zoals dit:

vector<vector<int> > IntV;

In 2006, met de release van platformversie 8.1, begonnen we Linux te ondersteunen en stapten we over op een standaardbibliotheek van derden STLPort. Een van de redenen voor de overstap was om met brede snaren te werken. We gebruiken std::wstring in onze code, die is gebaseerd op het type wchar_t. De grootte is standaard 2 bytes op Windows en 4 bytes op Linux. Dit leidde tot de incompatibiliteit van onze binaire protocollen tussen de client en de server, evenals verschillende persistente gegevens. Met gcc-opties kunt u specificeren dat de grootte van wchar_t tijdens compilatie ook 2 bytes is, maar dan kunt u de standaardbibliotheek van de compiler vergeten, omdat het gebruikt glibc, dat op zijn beurt is gecompileerd naar 4-byte wchar_t. Andere redenen waren een betere implementatie van standaardklassen, ondersteuning voor hashtabellen en zelfs emulatie van bewegingssemantiek in containers, waar we actief gebruik van maakten. En nog een reden, zoals ze last but not least zeggen, was de snaarprestaties. We hadden onze eigen klas voor strijkers, omdat Vanwege de specifieke kenmerken van onze software gebruiken we stringbewerkingen op zeer grote schaal en dit is van cruciaal belang voor ons.

Onze snaar is gebaseerd op de ideeën van snaaroptimalisatie die in de vroege jaren 2000 tot uiting kwamen Andrej Alexandrescu. Later, toen Alexandrescu bij Facebook werkte, werd op zijn suggestie een string gebruikt in de Facebook-engine die volgens vergelijkbare principes werkt (zie de bibliotheek dwaasheid).

Onze lijn gebruikte twee belangrijke optimalisatietechnologieën:

  1. Voor korte waarden wordt een interne buffer gebruikt in het stringobject zelf (geen extra geheugentoewijzing vereist).
  2. Voor alle anderen wordt mechanica gebruikt Kopiëren op schrijven. De waarde van de string wordt op één plaats opgeslagen, de referentietelling wordt gebruikt bij het toewijzen/wijzigen.

Om de compilatie van het platform te versnellen, hebben we de implementatie van stream (die we niet gebruikten) uitgesloten van onze STLPort-variant, wat ons een compilatiesnelheid van ongeveer 20% opleverde. Vervolgens moesten we beperkt gebruiken boost. Boost maakt uitgebreid gebruik van stream, met name in zijn service-API's (bijvoorbeeld voor logging), dus moesten we het aanpassen om het gebruik van stream ervan uit te sluiten. Dit maakte het op zijn beurt weer moeilijk voor ons om naar nieuwe versies van Boost te migreren.

derde manier

Bij de overgang naar de C++14-standaard hebben we de volgende opties overwogen:

  1. Verhoog de door ons aangepaste STLPort naar de C++ 14 standaard. De optie is erg moeilijk, omdat. STLPort-ondersteuning werd stopgezet in 2010 en we zouden alle code zelf moeten verwijderen.
  2. Verander naar een andere STL-implementatie die compatibel is met C++14. Het is zeer wenselijk dat deze implementatie onder Windows en Linux plaatsvindt.
  3. Gebruik bij het compileren voor elk besturingssysteem de bibliotheek die is ingebouwd in de overeenkomstige compiler.

De eerste optie werd direct afgewezen wegens te veel werk.

We hebben even nagedacht over de tweede optie; beschouwd als kandidaat libc++, maar op dat moment werkte het niet onder Windows. Om libc++ naar Windows te porten, zou je veel werk moeten verzetten - schrijf bijvoorbeeld zelf alles met betrekking tot threads, threadsynchronisatie en atomiciteit, aangezien libc++ in deze gebieden wordt gebruikt POSIX-API.

En we kozen voor de derde weg.

Overgang

We moesten dus het gebruik van STLPort vervangen door de bibliotheken van de overeenkomstige compilers (Visual Studio 2015 voor Windows, gcc 7 voor Linux, clang 8 voor macOS).

Gelukkig is onze code grotendeels volgens richtlijnen geschreven en geen gebruik gemaakt van allerlei lastige trucs, dus de migratie naar nieuwe bibliotheken verliep relatief soepel, met behulp van scripts die de namen van typen, klassen, naamruimten en insluitsels in bronbestanden vervingen. De migratie had betrekking op 10 bronbestanden (van de 000). wchar_t is vervangen door char14_t; we hebben besloten om te stoppen met het gebruik van wchar_t, omdat char000_t neemt 16 bytes in beslag op alle besturingssystemen en bederft de codecompatibiliteit tussen Windows en Linux niet.

Er waren wat kleine avonturen. In STLPort kan bijvoorbeeld een iterator impliciet worden gecast naar een elementpointer, en dit werd op sommige plaatsen in onze code gebruikt. In de nieuwe bibliotheken was dit niet meer mogelijk en moesten deze passages handmatig worden geanalyseerd en herschreven.

Dus de codemigratie is voltooid, de code is gecompileerd voor alle besturingssystemen. Het is tijd voor testen.

Tests na de overgang toonden een prestatiedaling (op sommige plaatsen tot 20-30%) en een toename van het geheugenverbruik (tot 10-15%) in vergelijking met de oude versie van de code. Dit was met name te wijten aan de suboptimale prestaties van standaard snaren. Daarom moesten we opnieuw onze eigen lijn gebruiken, enigszins aangepast.

Een interessant kenmerk van de implementatie van containers in ingebedde bibliotheken werd ook onthuld: lege (zonder elementen) std::map en std::set van ingebouwde bibliotheken wijzen geheugen toe. En in ons land worden, vanwege de eigenaardigheden van de implementatie, op sommige plaatsen van de code nogal wat lege containers van dit type gemaakt. Ze wijzen standaard geheugencontainers een beetje toe, voor één root-element, maar voor ons bleek het van cruciaal belang te zijn - in een aantal scenario's daalden onze prestaties aanzienlijk en nam het geheugenverbruik toe (vergeleken met STLPort). Daarom hebben we in onze code deze twee soorten containers uit de ingebouwde bibliotheken vervangen door hun implementatie van Boost, waar deze containers deze functie niet hadden, en dit loste het probleem met vertraging en verhoogd geheugenverbruik op.

Zoals vaak gebeurt na grootschalige wijzigingen in grote projecten, werkte de eerste iteratie van de bronnen niet zonder problemen, en hier waren we erg nuttig, met name ondersteuning voor debug-iterators in de Windows-implementatie. Stap voor stap gingen we verder en in het voorjaar van 2017 (versie 8.3.11 1C:Enterprise) was de migratie voltooid.

Resultaten van

De overgang naar de C++14-standaard kostte ons ongeveer 6 maanden. Meestal werkte één (maar zeer hooggekwalificeerde) ontwikkelaar aan het project en in de laatste fase voegden vertegenwoordigers van teams die verantwoordelijk waren voor specifieke gebieden zich bij - UI, servercluster, ontwikkelings- en beheertools, enz.

De overgang heeft ons werk bij het migreren naar de nieuwste versies van de standaard aanzienlijk vereenvoudigd. Versie 1C:Enterprise 8.3.14 (in ontwikkeling, de release staat gepland voor begin volgend jaar) is dus al overgezet naar de standaard C++17.

Na de migratie hebben ontwikkelaars meer opties. Als we eerder onze eigen aangepaste versie van STL en één std-naamruimte hadden, hebben we nu in de std-naamruimte standaardklassen van de ingebouwde bibliotheken van de compiler, in de stdx-naamruimte - onze regels en containers geoptimaliseerd voor onze taken, in boost - een frisse versie van boost. En de ontwikkelaar gebruikt die klassen die het meest geschikt zijn om zijn problemen op te lossen.

De "native" implementatie van move constructors helpt ook bij de ontwikkeling (constructeurs verplaatsen) voor een aantal klassen. Als een klasse een verplaatsingsconstructor heeft en deze klasse in een container wordt geplaatst, optimaliseert de STL het kopiëren van elementen in de container (bijvoorbeeld wanneer de container uitbreidt en u de capaciteit moet wijzigen en geheugen opnieuw moet toewijzen).

Vlieg in de zalf

Misschien wel het meest onaangename (maar niet kritieke) gevolg van migratie is dat we te maken krijgen met een toename van het volume obj-bestanden, en het volledige resultaat van de build met alle tussenliggende bestanden begon 60 - 70 GB in beslag te nemen. Dit gedrag is te wijten aan de eigenaardigheden van moderne standaardbibliotheken, die minder kritisch zijn geworden over het volume van gegenereerde servicebestanden. Dit heeft geen invloed op de werking van de gecompileerde applicatie, maar het veroorzaakt een aantal ongemakken bij de ontwikkeling, met name het verhoogt de compilatietijd. Er zijn ook verhoogde vereisten voor vrije schijfruimte op buildservers en ontwikkelmachines. Onze ontwikkelaars werken parallel aan verschillende versies van het platform en honderden gigabytes aan tussenliggende bestanden zorgen soms voor problemen bij het werk. Het probleem is onaangenaam, maar niet kritiek, we hebben de oplossing ervan voorlopig uitgesteld. Als een van de opties om het op te lossen, beschouwen we de techniek eenheid opbouwen (het wordt met name door Google gebruikt bij de ontwikkeling van de Chrome-browser).

Bron: www.habr.com

Voeg een reactie