Hvordan vi oversatte 10 millioner linjer C++-kode til C++14-standarden (og derefter til C++17)

For noget tid siden (i efteråret 2016), under udviklingen af ​​den næste version af teknologiplatformen 1C:Enterprise, opstod spørgsmålet i udviklingsteamet om at understøtte den nye standard C ++ 14 i vores kode. Overgangen til en ny standard, som vi antog, ville give os mulighed for at skrive mange ting mere elegant, enkelt og pålideligt og ville forenkle support og vedligeholdelse af koden. Og der synes ikke at være noget ekstraordinært i oversættelsen, hvis ikke for omfanget af kodebasen og de specifikke funktioner i vores kode.

For dem, der ikke ved det, er 1C:Enterprise et miljø for hurtig udvikling af cross-platform business-applikationer og runtime til deres eksekvering på forskellige OS'er og DBMS'er. Generelt indeholder produktet:

Vi forsøger at skrive den samme kode til forskellige operativsystemer så meget som muligt - serverkodebasen er 99% almindelig, klientkodebasen er omkring 95%. 1C:Enterprise teknologiplatformen er primært skrevet i C++ og omtrentlige kodekarakteristika er angivet nedenfor:

  • 10 millioner linjer C++ kode,
  • 14 tusind filer,
  • 60 tusind klasser,
  • en halv million metoder.

Og alle disse ting skulle oversættes til C++14. I dag vil vi fortælle dig, hvordan vi gjorde dette, og hvad vi stødte på i processen.

Hvordan vi oversatte 10 millioner linjer C++-kode til C++14-standarden (og derefter til C++17)

Ansvarsfraskrivelse

Alt skrevet nedenfor om langsomt/hurtigt arbejde, (ikke) stort hukommelsesforbrug ved implementeringer af standardklasser i forskellige biblioteker betyder én ting: dette er sandt FOR OS. Det er meget muligt, at standardimplementeringer vil være bedst egnede til dine opgaver. Vi tog udgangspunkt i vores egne opgaver: vi tog data, der var typiske for vores kunder, kørte typiske scenarier på dem, så på ydeevne, mængden af ​​forbrugt hukommelse osv., og analyserede, om vi og vores kunder var tilfredse med sådanne resultater eller ej. . Og de handlede afhængigt af.

hvad vi havde

I første omgang skrev vi koden til 1C:Enterprise 8 platformen i Microsoft Visual Studio. Projektet startede i begyndelsen af ​​2000'erne, og vi havde en kun Windows-version. Siden da er koden aktivt udviklet, mange mekanismer er naturligvis blevet fuldstændig omskrevet. Men koden blev skrevet efter 1998-standarden, og for eksempel var vores retvinklede parenteser adskilt af mellemrum, så kompileringen ville lykkes, sådan her:

vector<vector<int> > IntV;

I 2006, med udgivelsen af ​​platformversion 8.1, begyndte vi at understøtte Linux og skiftede til et tredjeparts standardbibliotek STLPort. En af årsagerne til overgangen var at arbejde med brede linjer. I vores kode bruger vi hele vejen igennem std::wstring, som er baseret på wchar_t-typen. Dens størrelse i Windows er 2 bytes, og i Linux er standarden 4 bytes. Dette førte til inkompatibilitet af vores binære protokoller mellem klient og server, såvel som forskellige vedvarende data. Ved at bruge gcc-indstillingerne kan du angive, at størrelsen af ​​wchar_t under kompilering også er 2 bytes, men så kan du glemme at bruge standardbiblioteket fra compileren, fordi den bruger glibc, som igen er kompileret til en 4-byte wchar_t. Andre årsager var bedre implementering af standardklasser, understøttelse af hashtabeller og endda emulering af semantikken ved at flytte inde i containere, som vi aktivt brugte. Og endnu en grund, som de siger sidst men ikke mindst, var strygeroptræden. Vi havde vores egen klasse for strygere, fordi... På grund af vores softwares specifikationer bruges strengoperationer meget bredt, og for os er dette afgørende.

Vores streng er baseret på idéer til strengoptimering udtrykt tilbage i begyndelsen af ​​2000'erne Andrei Alexandrescu. Senere, da Alexandrescu arbejdede hos Facebook, blev der efter hans forslag brugt en linje i Facebook-motoren, der arbejdede efter lignende principper (se biblioteket dårskab).

Vores linje brugte to hovedoptimeringsteknologier:

  1. Til korte værdier bruges en intern buffer i selve strengobjektet (kræver ikke yderligere hukommelsesallokering).
  2. For alle andre bruges mekanik Kopi på Skriv. Strengværdien gemmes ét sted, og der bruges en referencetæller under tildeling/ændring.

For at fremskynde platformkompileringen udelukkede vi streamimplementeringen fra vores STLPort-variant (som vi ikke brugte), dette gav os omkring 20% ​​hurtigere kompilering. Efterfølgende måtte vi gøre begrænset brug Boost. Boost gør stor brug af stream, især i dets service API'er (for eksempel til logning), så vi var nødt til at ændre det for at fjerne brugen af ​​stream. Dette gjorde det igen svært for os at migrere til nye versioner af Boost.

Tredje vej

Når vi flyttede til C++14-standarden, overvejede vi følgende muligheder:

  1. Opgrader den STLPort, vi modificerede til C++14-standarden. Muligheden er meget svær, fordi... support til STLPort blev afbrudt i 2010, og vi skulle selv bygge al dens kode.
  2. Overgang til en anden STL-implementering, der er kompatibel med C++14. Det er yderst ønskeligt, at denne implementering er til Windows og Linux.
  3. Når du kompilerer for hvert OS, skal du bruge biblioteket, der er indbygget i den tilsvarende compiler.

Den første mulighed blev blankt afvist på grund af for meget arbejde.

Vi tænkte på den anden mulighed i nogen tid; betragtes som kandidat libc++, men på det tidspunkt virkede det ikke under Windows. For at portere libc++ til Windows, skulle du gøre en masse arbejde - for eksempel skrive alt selv, der har at gøre med tråde, trådsynkronisering og atomicitet, da libc++ bruges i disse områder POSIX API.

Og vi valgte den tredje vej.

overgang

Så vi var nødt til at erstatte brugen af ​​STLPort med bibliotekerne i de tilsvarende compilere (Visual Studio 2015 til Windows, gcc 7 til Linux, clang 8 til macOS).

Heldigvis blev vores kode primært skrevet efter retningslinjer og brugte ikke alle mulige smarte tricks, så migreringen til nye biblioteker forløb forholdsvis gnidningsfrit, ved hjælp af scripts, der erstattede navnene på typer, klasser, navnerum og inkluderer i kilden filer. Migreringen påvirkede 10 kildefiler (ud af 000). wchar_t blev erstattet af char14_t; vi besluttede at opgive brugen af ​​wchar_t, fordi char000_t tager 16 bytes på alle OS'er og ødelægger ikke kodekompatibiliteten mellem Windows og Linux.

Der var nogle små eventyr. For eksempel kunne en iterator i STLPort implicit castes til en pointer til et element, og nogle steder i vores kode blev dette brugt. I nye biblioteker var det ikke længere muligt at gøre dette, og disse passager skulle analyseres og omskrives manuelt.

Så kodemigreringen er fuldført, koden er kompileret til alle operativsystemer. Det er tid til prøver.

Tests efter overgangen viste et fald i ydeevnen (nogle steder op til 20-30%) og en stigning i hukommelsesforbruget (op til 10-15%) sammenlignet med den gamle version af koden. Dette skyldtes især den suboptimale ydeevne af standardstrenge. Derfor måtte vi igen bruge vores egen, lidt modificerede linje.

Et interessant træk ved implementeringen af ​​containere i indlejrede biblioteker blev også afsløret: tomme (uden elementer) std::map og std::set fra indbyggede biblioteker allokerer hukommelse. Og på grund af implementeringsfunktionerne oprettes der nogle steder i koden ret mange tomme beholdere af denne type. Standardhukommelsesbeholdere er tildelt lidt til ét rodelement, men for os viste det sig at være kritisk - i en række scenarier faldt vores ydeevne betydeligt, og hukommelsesforbruget steg (sammenlignet med STLPort). Derfor erstattede vi i vores kode disse to typer containere fra de indbyggede biblioteker med deres implementering fra Boost, hvor disse containere ikke havde denne funktion, og det løste problemet med opbremsning og øget hukommelsesforbrug.

Som det ofte sker efter storstilede ændringer i store projekter, fungerede den første iteration af kildekoden ikke uden problemer, og her kom især support til fejlretning af iteratorer i Windows-implementeringen godt med. Trin for skridt kom vi videre, og i foråret 2017 (version 8.3.11 1C:Enterprise) var migreringen gennemført.

Resultaterne af

Overgangen til C++14-standarden tog os omkring 6 måneder. Det meste af tiden arbejdede en (men meget højt kvalificeret) udvikler på projektet, og i slutfasen sluttede repræsentanter for teams med ansvar for specifikke områder sig til - UI, serverklynge, udviklings- og administrationsværktøjer mv.

Overgangen forenklede i høj grad vores arbejde med at migrere til de nyeste versioner af standarden. Således er version 1C:Enterprise 8.3.14 (under udvikling, udgivelse planlagt til begyndelsen af ​​næste år) allerede blevet overført til standarden C++17.

Efter migreringen har udviklere flere muligheder. Hvis vi tidligere havde vores egen modificerede version af STL og et std-navneområde, har vi nu standardklasser fra de indbyggede compilerbiblioteker i std-navnerummet, i stdx-navnerummet - vores linjer og containere optimeret til vores opgaver, i boost - seneste version af boost. Og udvikleren bruger de klasser, der er optimalt egnede til at løse hans problemer.

Den "native" implementering af move constructors hjælper også med udviklingen (flytte konstruktører) for en række klasser. Hvis en klasse har en move constructor, og denne klasse er placeret i en container, så optimerer STL kopieringen af ​​elementer inde i containeren (f.eks. når containeren udvides, og det er nødvendigt at ændre kapacitet og omallokere hukommelse).

Malurt i bægeret

Den måske mest ubehagelige (men ikke kritiske) konsekvens af migration er, at vi står over for en stigning i volumen obj filer, og det fulde resultat af bygningen med alle mellemfilerne begyndte at fylde 60-70 GB. Denne adfærd skyldes de særlige kendetegn ved moderne standardbiblioteker, som er blevet mindre kritiske over for størrelsen af ​​genererede servicefiler. Dette påvirker ikke driften af ​​den kompilerede applikation, men det forårsager en række gener i udviklingen, især øger det kompileringstiden. Kravene til ledig diskplads på build-servere og på udviklermaskiner er også stigende. Vores udviklere arbejder på flere versioner af platformen parallelt, og hundredvis af gigabyte af mellemfiler skaber nogle gange vanskeligheder i deres arbejde. Problemet er ubehageligt, men ikke kritisk; vi har udskudt løsningen for nu. Vi betragter teknologi som en af ​​mulighederne for at løse det enhed opbygge (især Google bruger det, når Chrome-browseren udvikles).

Kilde: www.habr.com

Tilføj en kommentar