Hogyan fordítottunk le 10 millió sornyi C++ kódot a C++14 szabványra (majd a C++17-re)

Nemrég (2016 őszén), az 1C:Enterprise technológiai platform következő verziójának fejlesztése során felmerült a kérdés a fejlesztőcsapatban az új szabvány támogatásáról. C ++ 14 kódunkban. Az új szabványra való átállás, ahogy azt feltételeztük, sok mindent elegánsabban, egyszerűbben és megbízhatóbban írhatna le, és leegyszerűsítené a kód támogatását és karbantartását. És úgy tűnik, nincs semmi rendkívüli a fordításban, ha nem a kódbázis léptéke és a kódunk sajátosságai.

Azok számára, akik nem ismerik, az 1C:Enterprise egy olyan környezet, amely a többplatformos üzleti alkalmazások gyors fejlesztésére szolgál, és futási környezetet biztosít ezek különböző operációs rendszereken és DBMS-eken való futtatásához. Általánosságban a termék a következőket tartalmazza:

  • Alkalmazáskiszolgáló-fürt, Windows és Linux rendszeren fut
  • vásárló, http(-ek) vagy saját bináris protokollon keresztül működik a szerverrel, működik Windows, Linux, macOS rendszeren
  • Web kliens, Chrome, Internet Explorer, Microsoft Edge, Firefox, Safari böngészőkben fut (JavaScriptben írva)
  • Fejlesztőkörnyezet (konfigurátor), működik Windows, Linux és macOS rendszeren
  • Adminisztrációs eszközök alkalmazásszerverek, Windows, Linux, macOS rendszeren futnak
  • Mobil kliens, csatlakozik a szerverhez a http(ek)en keresztül, működik Android, iOS, Windows rendszerű mobileszközökön
  • Mobil platform — egy keretrendszer offline mobilalkalmazások létrehozásához szinkronizálási lehetőséggel, amely Android, iOS és Windows rendszeren fut
  • Fejlesztőkörnyezet 1C: Vállalati fejlesztési eszközök, Java nyelven írva
  • Сервер Interakciós rendszerek

Igyekszünk minél többet ugyanazt a kódot írni a különböző operációs rendszerekhez - a szerver kódbázis 99%-ban közös, a kliens kódbázis kb. 95%-ban. Az 1C:Enterprise technológiai platform elsősorban C++ nyelven íródott, és a hozzávetőleges kódjellemzők az alábbiak:

  • 10 millió sornyi C++ kód,
  • 14 ezer fájl,
  • 60 ezer osztály,
  • félmillió módszer.

És mindezt le kellett fordítani C++14-re. Ma elmondjuk, hogyan csináltuk ezt, és mivel találkoztunk a folyamat során.

Hogyan fordítottunk le 10 millió sornyi C++ kódot a C++14 szabványra (majd a C++17-re)

Jogi nyilatkozat

Az alábbiakban leírtak a lassú/gyors munkavégzésről, a (nem) nagy memóriafelhasználásról a szabványos osztályok implementációi által különböző könyvtárakban, egy dolgot jelent: ez NÁLUNK igaz. Elképzelhető, hogy a szabványos megvalósítások lesznek a legmegfelelőbbek az Ön feladataihoz. Saját feladatainkból indultunk ki: vettük az ügyfeleinkre jellemző adatokat, lefuttattunk rajtuk tipikus forgatókönyveket, megnéztük a teljesítményt, a felhasznált memória mennyiségét stb., és elemeztük, hogy mi és ügyfeleink elégedettek vagyunk-e ezzel az eredménnyel vagy sem. . És attól függően cselekedtek.

Ami nálunk volt

Kezdetben az 1C:Enterprise 8 platform kódját írtuk a Microsoft Visual Studio-ban. A projekt a 2000-es évek elején indult, és volt egy csak Windows-verziónk. Természetesen azóta a kódot aktívan fejlesztik, sok mechanizmust teljesen átírtak. De a kód az 1998-as szabvány szerint íródott, és például a derékszögű zárójeleinket szóközzel választottuk el, hogy a fordítás sikeres legyen, így:

vector<vector<int> > IntV;

2006-ban, a 8.1-es platformverzió megjelenésével megkezdtük a Linux támogatását, és áttértünk egy harmadik féltől származó szabványos könyvtárra STLPort. Az átállás egyik oka a széles vonalakkal való munka volt. Kódunkban az std::wstring karakterláncot használjuk, amely a wchar_t típuson alapul. A mérete Windowsban 2 bájt, Linuxban pedig 4 bájt. Ez a kliens és a szerver közötti bináris protokolljaink inkompatibilitásához, valamint különféle állandó adatokhoz vezetett. A gcc opciók segítségével megadhatod, hogy a wchar_t mérete fordítás közben is 2 bájt legyen, de akkor a fordítóból elfelejtheted a szabványos könyvtár használatát, mert glibc-t használ, ami viszont egy 4 bájtos wchar_t-hez van fordítva. További okok voltak a szabványos osztályok jobb megvalósítása, a hash táblák támogatása, és még a konténerek belsejében való mozgás szemantikájának emulációja is, amelyet aktívan használtunk. És még egy ok, ahogy mondják nem utolsósorban, a vonós előadás volt. Saját húróránk volt, mert... Szoftverünk sajátosságaiból adódóan a karakterlánc-műveletek nagyon széles körben használatosak, és ez számunkra kritikus.

Karakterláncunk a 2000-es évek elején megfogalmazott karakterlánc-optimalizálási ötleteken alapul Andrei Alexandrescu. Később, amikor Alexandrescu a Facebooknál dolgozott, az ő javaslatára egy sort használtak a Facebook motorban, amely hasonló elveken működött (lásd a könyvtárat ostobaság).

Cégünk két fő optimalizálási technológiát használt:

  1. Rövid értékek esetén magában a string objektumban lévő belső puffert használjuk (nem igényel további memóriafoglalást).
  2. Az összes többi esetében mechanikát használnak Másolás írásra. A karakterlánc értékét egy helyen tárolja, és referenciaszámlálót használ a hozzárendelés/módosítás során.

A platformfordítás felgyorsítása érdekében az STLPort változatunkból kizártuk a stream implementációt (amit nem használtunk), így körülbelül 20%-kal gyorsabb fordítást értünk el. Ezt követően korlátozottan kellett használnunk Növelje. A Boost nagymértékben használja a streamet, különösen a szolgáltatási API-kban (például naplózáshoz), ezért módosítanunk kellett a stream használatának megszüntetése érdekében. Ez viszont megnehezítette a Boost új verzióira való átállást.

harmadik út

Amikor a C++14 szabványra váltunk, a következő lehetőségeket vettük figyelembe:

  1. Frissítse az általunk módosított STLPortot a C++14 szabványra. A lehetőség nagyon nehéz, mert... Az STLPort támogatása 2010-ben megszűnt, és az összes kódját magunknak kell elkészítenünk.
  2. Áttérés egy másik, C++14-gyel kompatibilis STL implementációra. Nagyon kívánatos, hogy ez a megvalósítás Windowsra és Linuxra vonatkozzon.
  3. Az egyes operációs rendszerekhez való fordításkor használja a megfelelő fordítóba beépített könyvtárat.

Az első lehetőséget a túl sok munka miatt egyenesen elutasították.

Egy ideig gondolkodtunk a második lehetőségen; jelöltnek tekintik libc++, de akkoriban nem működött Windows alatt. A libc++ Windows-ra történő portolásához sok munkát kell végeznie – például mindent meg kell írnia, ami a szálakkal, a szálszinkronizálással és az atomitással kapcsolatos, mivel a libc++ ezeken a területeken használatos. POSIX API.

És mi a harmadik utat választottuk.

Átmenet

Tehát le kellett cserélnünk az STLPort használatát a megfelelő fordítók könyvtáraira (Visual Studio 2015 for Windows, gcc 7 for Linux, clang 8 for macOS).

Szerencsére a kódunk főként irányelvek szerint készült, és nem használt mindenféle okos trükköt, így viszonylag gördülékenyen zajlott a migráció az új könyvtárakra, olyan szkriptek segítségével, amelyek a forrásban a típusok, osztályok, névterek és belefoglalók neveit helyettesítették. fájlokat. Az áttelepítés 10 000 forrásfájlt érintett (a 14 000-ből). wchar_t helyére char16_t került; úgy döntöttünk, hogy felhagyunk a wchar_t használatával, mert A char16_t minden operációs rendszeren 2 bájtot vesz igénybe, és nem rontja el a Windows és a Linux közötti kódkompatibilitást.

Voltak apró kalandok. Például az STLPortban egy iterátort implicit módon egy elemre mutató mutatóra lehet önteni, és a kódunkban néhány helyen ezt használták. Az új könyvtárakban erre már nem volt lehetőség, ezeket a részeket kézzel kellett elemezni és átírni.

Tehát a kód áttelepítése befejeződött, a kód minden operációs rendszerhez le van fordítva. Itt az ideje a teszteknek.

Az átállás utáni tesztek teljesítménycsökkenést (néhol akár 20-30%-ot is), valamint memóriafelhasználás növekedését (akár 10-15%-kal) mutatták a kód régi verziójához képest. Ez különösen a szabványos húrok szuboptimális teljesítményének volt köszönhető. Ezért ismét saját, kissé módosított vonalat kellett használnunk.

A konténerek beágyazott könyvtárakban való megvalósításának egy érdekessége is kiderült: az üres (elemek nélküli) std::map és std::set a beépített könyvtárakból foglal le memóriát. És a megvalósítási jellemzők miatt a kódban néhol elég sok ilyen típusú üres konténer jön létre. A szabványos memóriakonténerek egy keveset foglalnak, egy gyökérelemhez, de számunkra ez kritikusnak bizonyult - számos esetben jelentősen csökkent a teljesítményünk, és nőtt a memóriafogyasztás (az STLPorthoz képest). Ezért a kódunkban ezt a két típusú konténert a beépített könyvtárakból helyettesítettük a Boostból való implementációjukkal, ahol ezek a konténerek nem rendelkeztek ezzel a funkcióval, és ez megoldotta a problémát a lassulás és a megnövekedett memóriafogyasztás miatt.

Amint az gyakran előfordul a nagy projektek nagyszabású változtatásai után, a forráskód első iterációja nem működött problémamentesen, és itt különösen jól jött a hibakeresési iterátorok támogatása a Windows megvalósításban. Lépésről lépésre haladtunk előre, és 2017 tavaszára (8.3.11 1C:Enterprise verzió) befejeződött a migráció.

Eredményei

A C++14 szabványra való átállás körülbelül 6 hónapig tartott. Legtöbbször egy (de nagyon magasan képzett) fejlesztő dolgozott a projekten, és a végső szakaszban egyes területekért felelős csapatok képviselői csatlakoztak - UI, szerverfürt, fejlesztői és adminisztrációs eszközök stb.

Az átállás nagyban leegyszerűsítette a szabvány legújabb verzióira való átállással kapcsolatos munkánkat. Így az 1C:Enterprise 8.3.14-es verzió (fejlesztés alatt, megjelenése jövő év elejére várható) már átkerült a szabványba C++17.

Az áttelepítés után a fejlesztőknek több lehetőségük van. Ha korábban rendelkeztünk saját módosított STL-verzióval és egy std névtérrel, most a beépített fordítókönyvtárak szabványos osztályai vannak az std névtérben, az stdx névtérben - soraink és konténereink a feladatainkra optimalizálva, boostban - a a boost legújabb verziója. A fejlesztő pedig azokat az osztályokat használja, amelyek optimálisan alkalmasak a problémáinak megoldására.

A mozgáskonstruktorok „natív” megvalósítása is segíti a fejlesztést (konstruktorok mozgatása) számos osztály esetében. Ha egy osztálynak van mozgatási konstruktora, és ez az osztály egy konténerben van elhelyezve, akkor az STL optimalizálja a tárolón belüli elemek másolását (például amikor a tárolót kibővítik, és szükség van a kapacitás módosítására és a memória átcsoportosítására).

Szépséghiba

A migráció talán legkellemetlenebb (de nem kritikus) következménye, hogy a volumen növekedésével nézünk szembe obj fájlokat, és a build teljes eredménye az összes köztes fájllal 60–70 GB-ot kezdett elfoglalni. Ez a viselkedés a modern szabványos könyvtárak sajátosságaiból adódik, amelyek kevésbé kritikusak a generált szolgáltatásfájlok méretével kapcsolatban. Ez nem befolyásolja a lefordított alkalmazás működését, de számos kellemetlenséget okoz a fejlesztésben, különösen megnöveli a fordítási időt. A build szervereken és a fejlesztői gépeken a szabad lemezterület iránti igény is növekszik. Fejlesztőink a platform több verzióján dolgoznak párhuzamosan, és a több száz gigabájtnyi köztes fájlok néha nehezítik a munkájukat. A probléma kellemetlen, de nem kritikus, megoldását egyelőre elhalasztottuk. A megoldás egyik lehetőségének tekintjük a technológiát egységépítés (különösen a Google használja a Chrome böngésző fejlesztésekor).

Forrás: will.com

Hozzászólás