ProHoster > Blog > Adminisztráció > Hogyan fordítottunk le 10 millió sornyi C++ kódot a C++14 szabványra (majd a C++17-re)
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:
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.
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:
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).
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:
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.
Á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.
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).