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

For en tid siden (høsten 2016), under utviklingen av neste versjon av teknologiplattformen 1C:Enterprise, oppsto spørsmålet i utviklingsteamet om å støtte den nye standarden C ++ 14 i koden vår. Overgangen til en ny standard, som vi antok, ville tillate oss å skrive mange ting mer elegant, enkelt og pålitelig, og ville forenkle støtte og vedlikehold av koden. Og det ser ikke ut til å være noe ekstraordinært i oversettelsen, hvis ikke for skalaen til kodebasen og de spesifikke egenskapene til koden vår.

For de som ikke vet, er 1C:Enterprise et miljø for rask utvikling av forretningsapplikasjoner på tvers av plattformer og kjøretid for kjøring av dem på forskjellige operativsystemer og DBMS-er. Generelt inneholder produktet:

  • Application Server Cluster, kjører på Windows og Linux
  • kunde, arbeider med serveren via http(er) eller sin egen binære protokoll, fungerer på Windows, Linux, macOS
  • Nettklient, kjører i Chrome, Internet Explorer, Microsoft Edge, Firefox, Safari-nettlesere (skrevet i JavaScript)
  • Utviklingsmiljø (Konfigurator), fungerer på Windows, Linux, macOS
  • Administrasjonsverktøy applikasjonsservere, kjører på Windows, Linux, macOS
  • Mobil klient, kobler til serveren via http(er), fungerer på mobile enheter som kjører Android, iOS, Windows
  • Mobil plattform — et rammeverk for å lage offline mobilapplikasjoner med muligheten til å synkronisere, kjører på Android, iOS, Windows
  • Utviklingsmiljø 1C:Bedriftsutviklingsverktøy, skrevet i Java
  • Сервер Interaksjonssystemer

Vi prøver å skrive den samme koden for forskjellige operativsystemer så mye som mulig - serverkodebasen er 99% vanlig, klientkodebasen er omtrent 95%. 1C:Enterprise teknologiplattformen er primært skrevet i C++ og omtrentlige kodekarakteristikker er gitt nedenfor:

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

Og alt dette måtte oversettes til C++14. I dag skal vi fortelle deg hvordan vi gjorde dette og hva vi møtte i prosessen.

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

Ansvarsfraskrivelse

Alt som er skrevet nedenfor om sakte/raskt arbeid, (ikke) stort minneforbruk ved implementeringer av standardklasser i ulike biblioteker betyr én ting: dette er sant FOR OSS. Det er godt mulig at standardimplementeringer vil være best egnet for oppgavene dine. Vi tok utgangspunkt i våre egne oppgaver: vi tok data som var typiske for våre kunder, kjørte typiske scenarier på dem, så på ytelse, forbrukt minne osv., og analyserte om vi og våre kunder var fornøyde med slike resultater eller ikke. . Og de handlet avhengig av.

Det vi hadde

Opprinnelig skrev vi koden for 1C:Enterprise 8-plattformen ved hjelp av Microsoft Visual Studio. Prosjektet startet tidlig på 2000-tallet, og vi hadde en versjon som bare var Windows. Naturligvis, siden den gang har koden blitt aktivt utviklet, mange mekanismer har blitt fullstendig omskrevet. Men koden ble skrevet i henhold til 1998-standarden, og for eksempel ble våre rettvinklede parenteser atskilt med mellomrom slik at kompileringen skulle lykkes, slik:

vector<vector<int> > IntV;

I 2006, med utgivelsen av plattformversjon 8.1, begynte vi å støtte Linux og byttet til et tredjeparts standardbibliotek STLPort. En av grunnene til overgangen var å jobbe med brede linjer. I koden vår bruker vi std::wstring, som er basert på wchar_t-typen, gjennomgående. Størrelsen i Windows er 2 byte, og i Linux er standard 4 byte. Dette førte til inkompatibilitet av våre binære protokoller mellom klient og server, samt ulike vedvarende data. Ved å bruke gcc-alternativene kan du spesifisere at størrelsen på wchar_t under kompilering også er 2 byte, men da kan du glemme å bruke standardbiblioteket fra kompilatoren, fordi den bruker glibc, som igjen er kompilert for en 4-byte wchar_t. Andre årsaker var bedre implementering av standardklasser, støtte for hash-tabeller og til og med emulering av semantikken ved å flytte inne i containere, som vi aktivt brukte. Og en annen grunn, som de sier sist, men ikke minst, var strengprestasjon. Vi hadde vår egen klasse for strykere, fordi... På grunn av spesifikasjonene til programvaren vår, brukes strengoperasjoner veldig mye, og for oss er dette kritisk.

Vår streng er basert på ideer om strengoptimalisering som ble uttrykt på begynnelsen av 2000-tallet Andrei Alexandrescu. Senere, da Alexandrescu jobbet på Facebook, etter hans forslag, ble det brukt en linje i Facebook-motoren som fungerte etter lignende prinsipper (se biblioteket dårskap).

Vår linje brukte to hovedoptimaliseringsteknologier:

  1. For korte verdier brukes en intern buffer i selve strengobjektet (krever ikke ekstra minneallokering).
  2. For alle andre brukes mekanikk Kopier på skriv. Strengverdien lagres på ett sted, og en referanseteller brukes under tildeling/modifikasjon.

For å få fart på plattformkompileringen ekskluderte vi strømimplementeringen fra vår STLPort-variant (som vi ikke brukte), dette ga oss omtrent 20 % raskere kompilering. Deretter måtte vi bruke begrenset Øke. Boost bruker mye strøm, spesielt i tjeneste-API-ene (for eksempel for logging), så vi måtte endre den for å fjerne bruken av strøm. Dette gjorde det igjen vanskelig for oss å migrere til nye versjoner av Boost.

Tredje vei

Når vi flyttet til C++14-standarden, vurderte vi følgende alternativer:

  1. Oppgrader STLPorten vi modifiserte til C++14-standarden. Alternativet er veldig vanskelig, fordi... støtte for STLPort ble avviklet i 2010, og vi måtte bygge all koden selv.
  2. Overgang til en annen STL-implementering som er kompatibel med C++14. Det er svært ønskelig at denne implementeringen er for Windows og Linux.
  3. Når du kompilerer for hvert OS, bruk biblioteket som er innebygd i den tilsvarende kompilatoren.

Det første alternativet ble direkte avvist på grunn av for mye arbeid.

Vi tenkte på det andre alternativet en stund; vurderes som en kandidat libc ++, men på det tidspunktet fungerte det ikke under Windows. For å portere libc++ til Windows, må du gjøre mye arbeid - for eksempel skrive alt selv som har med tråder, trådsynkronisering og atomitet å gjøre, siden libc++ brukes i disse områdene POSIX API.

Og vi valgte den tredje veien.

Переход

Så vi måtte erstatte bruken av STLPort med bibliotekene til de tilsvarende kompilatorene (Visual Studio 2015 for Windows, gcc 7 for Linux, clang 8 for macOS).

Heldigvis ble koden vår hovedsakelig skrevet i henhold til retningslinjer og brukte ikke alle slags smarte triks, så migreringen til nye biblioteker gikk relativt greit, ved hjelp av skript som erstattet navn på typer, klasser, navnerom og inkluderer i kilden filer. Migreringen påvirket 10 000 kildefiler (av 14 000). wchar_t ble erstattet av char16_t; vi bestemte oss for å forlate bruken av wchar_t, fordi char16_t tar 2 byte på alle operativsystemer og ødelegger ikke kodekompatibiliteten mellom Windows og Linux.

Det ble noen små eventyr. For eksempel, i STLPort kunne en iterator implisitt kastes til en peker til et element, og noen steder i koden vår ble dette brukt. I nye bibliotek var det ikke lenger mulig å gjøre dette, og disse passasjene måtte analyseres og omskrives manuelt.

Så kodemigreringen er fullført, koden er kompilert for alle operativsystemer. Det er tid for tester.

Tester etter overgangen viste et fall i ytelse (noen steder opptil 20-30%) og en økning i minneforbruk (opptil 10-15%) sammenlignet med den gamle versjonen av koden. Dette var spesielt på grunn av den suboptimale ytelsen til standardstrenger. Derfor måtte vi igjen bruke vår egen, litt modifiserte linje.

Et interessant trekk ved implementeringen av containere i innebygde biblioteker ble også avslørt: tomme (uten elementer) std::map og std::set fra innebygde biblioteker allokerer minne. Og på grunn av implementeringsfunksjonene opprettes det noen steder i koden ganske mange tomme beholdere av denne typen. Standard minnebeholdere er tildelt litt for ett rotelement, men for oss viste dette seg å være kritisk - i en rekke scenarier falt ytelsen vår betydelig og minneforbruket økte (sammenlignet med STLPort). Derfor erstattet vi i koden vår disse to typene containere fra de innebygde bibliotekene med deres implementering fra Boost, hvor disse containerne ikke hadde denne funksjonen, og dette løste problemet med nedbremsing og økt minneforbruk.

Som ofte skjer etter store endringer i store prosjekter, fungerte ikke den første iterasjonen av kildekoden uten problemer, og her kom særlig støtte for feilsøking av iteratorer i Windows-implementeringen godt med. Steg for steg gikk vi videre, og våren 2017 (versjon 8.3.11 1C:Enterprise) var migreringen fullført.

Resultater av

Overgangen til C++14-standarden tok oss omtrent 6 måneder. Mesteparten av tiden jobbet en (men svært høyt kvalifisert) utvikler med prosjektet, og i sluttfasen ble representanter for team ansvarlige for spesifikke områder med - UI, serverklynge, utviklings- og administrasjonsverktøy, etc.

Overgangen forenklet arbeidet vårt med å migrere til de nyeste versjonene av standarden. Dermed er versjon 1C:Enterprise 8.3.14 (under utvikling, utgivelse planlagt tidlig neste år) allerede blitt overført til standarden C++17.

Etter migreringen har utviklerne flere alternativer. Hvis vi tidligere hadde vår egen modifiserte versjon av STL og ett std-navneområde, har vi nå standardklasser fra de innebygde kompilatorbibliotekene i std-navneområdet, i stdx-navnerommet - våre linjer og containere optimalisert for oppgavene våre, i boost - siste versjon av boost. Og utvikleren bruker de klassene som er optimalt egnet for å løse problemene hans.

Den "innfødte" implementeringen av flyttekonstruktører hjelper også i utviklingen (flytte konstruktører) for en rekke klasser. Hvis en klasse har en flyttekonstruktør og denne klassen er plassert i en container, optimaliserer STL kopieringen av elementer inne i containeren (for eksempel når containeren utvides og det er nødvendig å endre kapasitet og omfordele minne).

Fly i salven

Den kanskje mest ubehagelige (men ikke kritiske) konsekvensen av migrasjon er at vi står overfor en økning i volumet obj filer, og det fulle resultatet av byggingen med alle mellomfilene begynte å ta opp 60–70 GB. Denne oppførselen skyldes særegenhetene til moderne standardbiblioteker, som har blitt mindre kritiske til størrelsen på genererte tjenestefiler. Dette påvirker ikke driften av den kompilerte applikasjonen, men det forårsaker en rekke ulemper i utviklingen, spesielt øker det kompileringstiden. Kravene til ledig diskplass på byggeservere og på utviklermaskiner øker også. Utviklerne våre jobber på flere versjoner av plattformen parallelt, og hundrevis av gigabyte med mellomfiler skaper noen ganger vanskeligheter i arbeidet deres. Problemet er ubehagelig, men ikke kritisk; vi har utsatt løsningen for nå. Vi vurderer teknologi som et av alternativene for å løse det enhet bygge (spesielt Google bruker det når du utvikler Chrome-nettleseren).

Kilde: www.habr.com

Legg til en kommentar