Hur vi översatte 10 miljoner rader C++-kod till standard C++14 (och sedan till C++17)

För en tid sedan (hösten 2016), nĂ€r vi utvecklade en ny version av teknologiplattformen 1C:Enterprise, kom frĂ„gan om att stödja den nya standarden upp inom utvecklingsteamet. C++14 i vĂ„r kod. ÖvergĂ„ngen till den nya standarden, som vi antog, skulle lĂ„ta oss skriva mĂ„nga saker mer elegant, enklare och mer pĂ„litligt, och förenkla underhĂ„ll och support av koden. Det kan verka ointressant i översĂ€ttning, om det inte vore för storleken pĂ„ kodbasen och specifika egenskaper i vĂ„r kod.

För dem som inte vet, Àr 1C:Enterprise en miljö för snabb utveckling av plattformsoberoende affÀrsapplikationer och en runtime för att köra dem pÄ olika operativsystem och databashanterare. I stora drag innehÄller produkten följande:

  • Klass av applikationsservrar, fungerar pĂ„ Windows och Linux
  • Klient, som kommunicerar med servern via http(s) eller via egen binĂ€r protokoll, fungerar pĂ„ Windows, Linux, macOS
  • Webbkund, som fungerar i webblĂ€sarna Chrome, Internet Explorer, Microsoft Edge, Firefox, Safari (skriven i JavaScript)
  • Utvecklingsmiljö (Konfigurator), fungerar pĂ„ Windows, Linux, macOS
  • Administrationsverktyg för applikationsservrar, fungerar pĂ„ Windows, Linux, macOS
  • Mobilklient, ansluter till servern via http(s), fungerar pĂ„ mobila enheter som kör Android, iOS, Windows
  • Mobilplattform — ett ramverk för att skapa offline-mobilapplikationer med synkroniseringsmöjligheter, fungerande pĂ„ Android, iOS, Windows
  • Utvecklingsmiljö 1C:Enterprise Utvecklingsverktyg, skriven i Java
  • Servern Interaktionssystem

Vi strĂ€var efter att maximalt skriva en enda kod för olika operativsystem — kodbasen för servern Ă€r gemensam till 99%, klienten till ungefĂ€r 95%. Teknologiplattformen 1C:Enterprise Ă€r huvudsakligen skriven i C++ och nedan följer ungefĂ€rliga egenskaper för koden:

  • 10 miljoner rader C++ kod,
  • 14 000 filer,
  • 60 000 klasser,
  • en halv miljon metoder.

Och hela detta system mÄste översÀttas till C++14. Idag kommer vi att berÀtta om hur vi gjorde detta och vilka utmaningar vi stötte pÄ under processen.

Hur vi översatte 10 miljoner rader C++-kod till standard C++14 (och sedan till C++17)

Disclaimer

Det som skrivs nedan om lÄngsamt/snabbt arbete och (inte) stort minnesanvÀndande av standardklassimplementationer i olika bibliotek gÀller ENDAST för OSS. Det Àr fullt möjligt att de standardimplementationer som finns passar bÀst för dina behov. Vi utgick ifrÄn vÄra behov: vi tog typiska data frÄn vÄra kunder, körde typiska scenarier pÄ dem, tittade pÄ prestanda, minnesanvÀndning osv., och analyserade om resultaten uppfyllde vÄra och vÄra kunders krav eller inte. Och vi agerade beroende pÄ det.

Vad vi hade

Initialt skrev vi koden för plattformen 1C:Enterprise 8 i Microsoft Visual Studio. Projektet började i början av 2000-talet och vi hade bara en version för Windows. Naturligtvis har koden utvecklats mycket sedan dess, mÄnga mekanismer har skrivits om helt. Men koden skrevs enligt standar 1998, sÄ vi hade till exempel högra vinkelparenteser separerade med mellanslag för att kompileringen skulle gÄ igenom, sÄ hÀr:

vector<vector > IntV;

År 2006, med lanseringen av plattformversion 8.1, började vi stödja Linux och övergick till ett tredjeparts standardbibliotek STLPort. En av anledningarna till övergĂ„ngen var arbetet med breda strĂ€ngar. I vĂ„r kod anvĂ€nder vi överallt std::wstring, som baseras pĂ„ typen wchar_t. Dess storlek i Windows Ă€r 2 byte, men i Linux Ă€r den som standard 4 byte. Detta ledde till inkompatibilitet av vĂ„ra binĂ€ra protokoll mellan klient och server, samt olika permanenta data. Med gcc:s alternativ kan man ange att storleken pĂ„ wchar_t under kompilering ocksĂ„ ska vara 2 byte, men dĂ„ fĂ„r man glömma att anvĂ€nda standardbiblioteket frĂ„n kompilatorn, eftersom det anvĂ€nder glibc, som i sin tur Ă€r kompilera för 4-byte wchar_t. Andra anledningar var en mer kvalitativ implementation av standardklasser, stöd för hash-tabeller och till och med emulering av flyttsemantik inom containrar, vilket vi anvĂ€nde flitigt. Och en annan anledning, som man sĂ€ger, last but not least, var prestandan hos strĂ€ngarna. Vi hade vĂ„r egen klass för strĂ€ngar, eftersom strĂ€ngoperationer anvĂ€nds mycket brett i vĂ„r mjukvaras specifik och Ă€r kritiska för oss.

VÄr strÀng Àr baserad pÄ idéer om optimering av strÀngar, som formulerades redan i början av 2000-talet Andrei Alexandrescu. Senare, nÀr Alexandrescu arbetade pÄ Facebook, anvÀndes en rad i Facebooks motor pÄ liknande principer (se biblioteket folly).

. I vÄr rad anvÀndes tvÄ huvudsakliga optimeringstekniker:

  1. För korta vÀrden anvÀnds en intern buffert i sjÀlva strÀngobjektet (som inte krÀver extra minnesallokering).
  2. För alla andra anvÀnds mekanismen Copy On Write. VÀrdet av strÀngen lagras pÄ ett stÀlle, och vid tilldelning/modifiering anvÀnds en referensrÀknare.

För att pÄskynda kompileringen av plattformen uteslöt vi stream-implementeringen frÄn vÄr version av STLPort (som vi inte anvÀnde), vilket gav oss cirka 20 % snabbare kompilering. Som en följd av detta var vi tvungna att anvÀnda Boost. Boost anvÀnder aktivt stream, sÀrskilt i sina tjÀnst-API:er (till exempel för loggning), sÄ vi var tvungna att modifiera det genom att utesluta anvÀndningen av stream. Detta gjorde det i sin tur svÄrare för oss att migrera till nya versioner av Boost.

Det tredje alternativet

Vid övergÄngen till standard C++14 övervÀgde vi följande alternativ:

  1. Att bygga upp vÄr modifierade STLPort pÄ standard C++14. Detta alternativ Àr mycket komplicerat, eftersom stödet för STLPort upphörde 2010, och vi skulle behöva lyfta hela dess kod sjÀlva.
  2. ÖvergĂ„ng till en annan STL-implementation som Ă€r kompatibel med C++14. Det Ă€r mycket önskvĂ€rt att denna implementation fungerar pĂ„ Windows och Linux.
  3. Att anvÀnda den inbyggda biblioteket i respektive kompilator vid kompilering för varje OS.

Det första alternativet avfÀrdades genast pÄ grund av den alltför stora arbetsmÀngden.

Vi funderade ett tag pĂ„ det andra alternativet; som kandidat övervĂ€gde vi libc++, men den fungerade vid den tidpunkten inte pĂ„ Windows. För att porta libc++ till Windows skulle det krĂ€vas en hel del arbete — till exempel skulle vi behöva skriva allt som rör trĂ„dar, trĂ„d-synkronisering och atomaritet, eftersom libc++ anvĂ€nde POSIX API.

Och vi valde det tredje alternativet.

ÖvergĂ„ngen

SÄ vi skulle ersÀtta anvÀndningen av STLPort med biblioteken frÄn respektive kompilatorer (Visual Studio 2015 för Windows, gcc 7 för Linux, clang 8 för macOS).

Lyckligtvis skrevs vÄr kod huvudsakligen enligt riktlinjerna och anvÀnde inte nÄgra kluriga tricks, sÄ migreringen till de nya biblioteken gick ganska smidigt, med hjÀlp av skript som ersatte typ-, klass-, namnrymds- och inkluderingsnamn i kÀllfilerna. Migreringen berörde 10 000 kÀllfiler (av 14 000). wchar_t ersattes med char16_t; vi beslutade att avstÄ frÄn att anvÀnda wchar_t, eftersom char16_t tar 2 byte pÄ alla operativsystem och inte stör kodens kompatibilitet mellan Windows och Linux.

Det blev dock inte utan smÄ Àventyr. Till exempel kunde man i STLPort implicit typkonvertera en iterator till en pekare pÄ ett element, och pÄ vissa stÀllen i vÄr kod anvÀndes detta. I de nya biblioteken gick det inte lÀngre att göra sÄ, och dessa stÀllen fick vi analysera och skriva om manuellt.

SÄ, kodmigreringen Àr avslutad, koden kompileras för alla operativsystem. Det Àr dags för tester.

Tester efter övergÄngen visade en nedgÄng i prestanda (i vissa fall upp till 20-30%) och en ökning av minnesanvÀndningen (upp till 10-15%) jÀmfört med den gamla kodversionen. Detta berodde delvis pÄ den suboptimala hanteringen av standardstrÀngar. DÀrför var vi tvungna att Äterigen anvÀnda vÄr egen strÀng, nÄgot modifierad.

Det avslöjades ocksĂ„ en intressant egenskap hos implementeringen av behĂ„llare i inbyggda bibliotek: tomma (utan element) std::map och std::set frĂ„n de inbyggda biblioteken allokerar minne. Och i vissa delar av vĂ„r kod skapas det ganska mĂ„nga tomma behĂ„llare av denna typ pĂ„ grund av implementationens sĂ€rdrag. De standardbehĂ„llare som allokerar minne gör det lite, för ett rot-element, men för oss blev detta kritiskt – under flera scenarier minskade vi prestandan avsevĂ€rt och ökade minnesanvĂ€ndningen (jĂ€mfört med STLPort). DĂ€rför ersatte vi dessa tvĂ„ typer av behĂ„llare frĂ„n de inbyggda biblioteken med deras implementation frĂ„n Boost, dĂ€r dessa behĂ„llare inte hade denna egenskap, och det löste problemet med nedgĂ„ng och ökad minnesanvĂ€ndning.

Som det ofta Àr fallet efter omfattande förÀndringar i stora projekt fungerade första iterationen av kÀllkoden inte utan problem, och hÀr blev stödet för debuggenomgÄngar i Windows-implementationen mycket anvÀndbart. Steg för steg gick vi framÄt, och i vÄr 2017 (version 8.3.11 frÄn 1C: Enterprise) var migreringen avslutad.

Sammanfattning

ÖvergĂ„ngen till standard C++14 tog oss cirka 6 mĂ„nader. Under större delen av projektet arbetade en (men mycket kvalificerad) utvecklare ensam, och i den avslutande fasen anslöt sig representanter frĂ„n teamen ansvariga för specifika omrĂ„den – UI, kluster servrar, utvecklings- och administreringsverktyg osv.

ÖvergĂ„ngen har kraftigt förenklat vĂ„rt arbete med att migrera till de nyaste versionerna av standarden. SĂ„ledes Ă€r version 1C:Enterprise 8.3.14 (under utveckling, release planerad till början av nĂ€sta Ă„r) redan översatt till standarden C++17.

Efter migreringen har utvecklarna fĂ„tt fler möjligheter. Förr hade vi vĂ„r egen modifierade version av STL och ett namnomrĂ„de std, men nu innehĂ„ller namnomrĂ„det std standardklasser frĂ„n kompilatorns inbyggda bibliotek, i namnomrĂ„det stdx – vĂ„ra, optimerade för vĂ„ra uppgifter, strĂ€ngar och behĂ„llare, i boost – den senaste versionen av boost. Utvecklaren anvĂ€nder de klasser som optimalt passar för att lösa hans uppgifter.

Den inbyggda implementeringen av flyttkonstruktörer hjÀlper ocksÄ till i utvecklingen (move constructors) för ett antal klasser. Om en klass har en flyttkonstruktör och denna klass placeras i en behÄllare, sÄ optimerar STL kopieringen av element inuti behÄllaren (till exempel nÀr behÄllaren expanderar och kapaciteten behöver Àndras och minnet mÄste allokeras pÄ nytt).

En sked te

Det som nog Ă€r det mest obehagliga (men inte kritiska) resultatet av migreringen – vi har stött pĂ„ en ökning av mĂ€ngden obj-filer, och det totala resultatet av bygget med alla mellanliggande filer har blivit 60–70 GB. Detta beteende Ă€r relaterat till egenskaperna hos moderna standardbibliotek, som blivit mindre kritiska nĂ€r det gĂ€ller mĂ€ngden genererade hjĂ€lpfiler. Detta pĂ„verkar inte arbetet med den kompilerade applikationen, men medför en rad olĂ€genheter i utvecklingen, sĂ€rskilt ökar compilationstiden. Kraven pĂ„ ledigt diskutrymme pĂ„ byggservrar och utvecklarens maskiner har ocksĂ„ ökat. VĂ„ra utvecklare arbetar parallellt med flera versioner av plattformen, och hundratals gigabyte mellanliggande filer skapar ibland svĂ„righeter i arbetet. Problemet Ă€r obehagligt, men inte kritiskt, och vi har för tillfĂ€llet skjutit upp lösningen. Som ett av alternativen för att lösa det övervĂ€ger vi tekniken unity build (det, bland annat, anvĂ€nder Google vid utvecklingen av webblĂ€saren Chrome).

KĂ€lla: habr.com

Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar đŸ”„ Köp pĂ„litlig webbhotell med DDoS-skydd, VPS VDS-servrar | ProHoster