Néhány idővel ezelőtt (2016 őszén) az 1C:Enterprise technológiai platform új verziójának fejlesztése során a fejlesztőcsapatban felmerült a kérdés, hogy támogassák az új szabványt. a kódunkban. Az új szabványra való átállás, ahogyan feltételeztük, lehetővé tette, hogy sok dolgot elegánsabban, egyszerűbben és megbízhatóbban írjunk, megkönnyítve a kód karbantartását és támogatását. A fordításban látszólag nincs semmi különös, ha nem vesszük figyelembe a kódalap méretét és a kódunk specifikus jellemzőit.
Akik nem tudják, az 1C:Enterprise egy gyors fejlesztési környezet a multiplatform üzleti alkalmazások számára, és runtime, amely lehetővé teszi azok végrehajtását különböző operációs rendszereken és adatbázis-kezelő rendszereken. Általános értelemben a termék tartalmazza:
- , amely Windows és Linux alatt működik
- , amely a szerverrel http(s) vagy saját bináris protokollján keresztül kommunikál, működik Windows, Linux, macOS rendszereken
- , amely a Chrome, Internet Explorer, Microsoft Edge, Firefox, Safari böngészőkben működik (JavaScript nyelven készült)
- Fejlesztői környezet (), amely Windows, Linux, macOS rendszereken működik
- alkalmazás szerverekhez, amelyek Windows, Linux, macOS rendszereken működnek
- , amely http(s) kapcsolaton keresztül csatlakozik a szerverhez, mobil eszközökön Android, iOS, Windows alatt működik
- — keretrendszer offline mobilalkalmazások létrehozásához szinkronizálási lehetőséggel, Android, iOS, Windows rendszereken működik
- Fejlesztői környezet , Java nyelven íródott
- Szerver
Maksimumra törekszünk, hogy egyetlen kódot írjunk különböző OS-ekhez — a szerver kódalap 99%-ban közös, az ügyfél pedig körülbelül 95%-ban. Az 1C:Enterprise technológiai platform főként C++ nyelven íródott, és az alábbiakban feltüntetett kód jellemzői:
- 10 millió sor C++ kód,
- 14 ezer fájl,
- 60 ezer osztály,
- félmillió metódus.
És mindezt a C++14-re kellett fordítani. Arról, hogy ezt hogyan csináltuk és mikkel találkoztunk a folyamat során, ma fogunk beszélni.

Megjegyzés
Minden, amit az alábbiakban írtunk a lassú\/gyors működésről, a (nem) nagy memóriafogyasztású standard osztályok megvalósításáról különböző könyvtárakban, egyet jelent: ez ránk igaz. Lehet, hogy az Ön feladataihoz a standard megvalósítások a legjobban megfelelnek. Mi a saját feladatainkra alapozva dolgoztunk: tipikus ügyféladatokat használtunk, tipikus forgatókönyveken futtattuk őket, figyeltük a sebességet, a memóriafogyasztást stb., és elemeztük, hogy ezek az eredmények megfelelnek-e számunkra és ügyfeleink számára. Ennek megfelelően jártunk el.
Ami nálunk volt
Kezdetben a 1C:Enterprise 8 platform kódját a Microsoft Visual Studio alatt írtuk. A projekt a 2000-es évek elején indult, és csak Windows verziónk volt. Természetesen azóta a kód aktívan fejlődött, sok mechanizmus teljesen újraírásra került. De a kód 1998-as szabvány szerint készült, és például a jobboldali szögletes zárójelek között voltak szóközök a sikeres fordítás érdekében, így:
vector<vector > IntV;2006-ban, a 8.1-es verzió kiadásával elkezdtük támogatni a Linuxot, és áttértünk egy harmadik féltől származó standard könyvtárra . Az áttérés egyik oka a széles karakterláncokkal való munka volt. A kódunkban széles körben használjuk a std::wstring-et, amely a wchar_t típusra alapoz. Windows alatt a mérete 2 byte, míg Linux alatt alapértelmezés szerint 4 byte. Ez inkonzisztenciához vezetett a kliens és a szerver közötti bináris protokolljaink, valamint különböző tartós adatok között. A gcc opcióival meg lehet adni, hogy a wchar_t mérete is 2 byte legyen a fordítás során, de akkor a fordító standard könyvtárának használatáról le lehet mondani, mivel az glibc-t használ, amely viszont 4 byte-os wchar_t-ra lett fordítva. Más okok között szerepelt a standard osztályok jobb megvalósítása, a hash-táblák támogatása, és még a konténerek belsejében a mozgás szemantika emulálása is, amelyet aktívan használtunk. Továbbá, ahogy mondani szokták, last but not least, a karakterláncok teljesítménye volt az egyik oka. Saját karakterlánc osztályunk volt, mivel a szoftverünk specifikuma miatt a karakterlánc műveleteket széles körben használják, ami kritikus számunkra.
A karakterláncunk a karakterláncok optimalizálásának ötletein alapul, amelyeket már a 2000-es évek elején megfogalmaztak . Később, amikor Alexandrescu a Facebooknál dolgozott, az ő kezdeményezésére a Facebook motorjában hasonló elven működő sztringet használtak (lásd könyvtár ).
A mi sztringünkben két fő optimalizáló technológia volt:
- Rövid értékek esetén a sztring objektumon belül tárolt belső puffer használatos (ami nem igényel további memóriaallokálást).
- Minden egyéb esetben a mechanizmus . A sztring értéke egy helyen tárolódik, asignálás/módosítás esetén hivatkozás számláló használatos.
A platform fordításának felgyorsítása érdekében eltávolítottuk STLPort-unk stream megvalósítását (amit nem használtunk), ez körülbelül 20%-os fordítási gyorsulást eredményezett. Később korlátozottan használnunk kellett . A Boost aktívan használja a stream-et, különösen a szolgáltatási API-jában (például naplózásra), ezért módosítanunk kellett, hogy kiiktassuk a stream használatát. Ez viszont megnehezítette az új Boost verziókhoz való átállást.
Harmadik út
A C++14 szabványra való áttérés során a következő lehetőségeket vizsgáltuk:
- A módosított STLPort-unkat C++14 szabványra emelni. Ez a lehetőség nagyon nehéz, mivel az STLPort támogatását 2010-ben megszüntették, és az összes kódot nekünk kellett volna felemelni.
- Áttérni egy C++14-kompatibilis STL másik megvalósítására. Előnyös lenne, ha ez a megvalósítás Windows és Linux alatt is működne.
- Használni a megfelelő fordító beépített könyvtárát minden OP fordításakor.
Az első lehetőséget azonnal elvetettük a túl nagy munkamennyiség miatt.
Néhány ideig a második lehetőségen tűnődtünk; mint jelöltet , de akkoriban nem működött Windows alatt. A libc++ Windowsra való portolásához jelentős munkát kellett volna elvégeznünk — például nekünk kellett volna írni mindent, ami a szálakkal, szálak szinkronizációjával és atomossággal kapcsolatos, mivel a libc++ ezen a területen .
Így mi a harmadik utat választottuk.
Átállás
Tehát az STLPort használatának a megfelelő fordítókra (Visual Studio 2015 Windows-ra, gcc 7 Linux-ra, clang 8 macOS-ra) való cseréjét kellett elvégeznünk.
Szerencsére a kódunk alapvetően a guidelineok szerint készült, és nem használt különféle trükköket, így a új könyvtárakra való migráció viszonylag zökkenőmentesen zajlott, köszönhetően a szkripteknek, amelyek az eredeti fájlokban cserélték az adattípusok, osztályok, névterek és include-ok neveit. A migráció 10 000 forrásfájlt érintett (14 000-ből). A wchar_t típust char16_t-re cseréltük; úgy döntöttünk, hogy lemondunk a wchar_t használatáról, mivel a char16_t minden operációs rendszeren 2 bájt helyet foglal el, és nem rontja a kód kompatibilitását Windows és Linux között.
Nem múlt el kis kalandok nélkül. Például az STLPort-ban az iterátorokat implicit módon lehetett pointerré castolni, és a kódunk egyes részein ezt használtuk. Az új könyvtárakban ezt már nem lehetett így csinálni, és ezeket a részeket elemezni és manuálisan átírni kellett.
Tehát a kód migrációja befejeződött, a kód minden operációs rendszeren lefordul. Itt az ideje a teszteknek.
A migráció utáni tesztek teljesítménycsökkenést mutattak (helyenként 20-30% -ot) és megnövekedett memóriafogyasztást (10-15% -ot) a régi kód verzióhoz képest. Ez elsősorban a standard stringek nem optimális működésével volt összefüggésben. Ezért a stringet ismét saját, kissé továbbfejlesztett verzióra kellett cserélnünk.
Egy érdekes sajátosságot is felfedeztünk az embedded könyvtárak konténerimplementációjában: az üres (elemek nélküli) std::map és std::set a beépített könyvtárakból memóriát allokálnak. Nálunk a megvalósítás sajátosságai miatt a kód egyes helyein viszonylag sok üres konténer jön létre ebből a típusból. A standard konténerek meglehetősen minimális memóriát allokálnak, egy gyökérelem számára, de számunkra ez kritikusnak bizonyult – egyes forgatókönyvekben a teljesítményünk érzékelhetően csökkent és a memóriafogyasztás megnövekedett (a STLPort-hoz képest). Ezért a kódunkban ezeket a két típusú konténert a beépített könyvtárakból lecseréltük a Boost implementációjára, ahol ezek a konténerek nem rendelkeztek ezzel a tulajdonsággal, és ez megoldotta a lassulás és a megnövekedett memóriafogyasztás problémáját.
Ahogy az gyakran történik nagyobb projektekben végrehajtott átfogó változások után, az első iterációk nem voltak problémák nélkül, és itt különösen hasznos volt a Windows implementációjában a debug iterátorok támogatása. Lépésről lépésre haladtunk előre, és 2017 tavaszára (1C:Enterprise 8.3.11 verzió) a migráció befejeződött.
Összegzés
A C++14 szabványra való áttérés körülbelül 6 hónapot vett igénybe. A projekt munkájának nagy részét egy (de nagyon magasan képzett) fejlesztő végezte, míg a végső szakaszban csatlakoztak a csapat képviselői, akik konkrét területekért feleltek — UI, klaszter szerverek, fejlesztési és adminisztrációs eszközök stb.
Az áttérés jelentősen egyszerűsítette a legújabb szabványokra való migrálásunkat. Így például az 1C:Enterprise 8.3.14 verzió (fejlesztés alatt, a kiadás a következő év elejére van tervezve) már át lett ültetve a szabványra. .
A migráció után a fejlesztőknek számos új lehetőségük akadt. Míg korábban saját változtatott STL verziónk és egy std névtér volt, most a std névtérben a fordító beépített könyvtáraiból származó standard osztályok találhatók, a stdx névtérben a saját, a feladatainkhoz optimalizált sztringek és konténerek, a boost pedig a legfrissebb boost verziót tartalmazza. A fejlesztő azokat az osztályokat használhatja, amelyek optimálisan illeszkednek a feladatmegoldásához.
A fejlesztést a "natív" mozgási konstruktőrök megvalósítása is segíti () több osztály esetében. Ha egy osztálynak van mozgási konstruktőre, és ezt az osztályt tartalmazó konténerbe helyezzük, akkor az STL optimalizálja az elemek másolását a konténeren belül (például amikor a konténer bővül és meg kell változtatni a capacity-t és a memóriát reallocálni).
Egy kanál kátrány
A migráció legvalószínűbb kellemetlen (de nem kritikus) következménye az volt, hogy nőtt a , és a build teljes eredménye az összes közbenső fájllal együtt 60-70 GB-ot foglalt el. Ez a viselkedés a modern standard könyvtárak sajátosságaival magyarázható, amelyek kevésbé kritikusak a generált segédfájlok mennyiségével szemben. Ez nem befolyásolja a lefordított alkalmazás működését, de számos kényelmetlenséget okoz a fejlesztés során, különösen növeli a fordítási időt. A build szervereken és a fejlesztők gépein is nőnek a szabad lemezterület követelményei. Fejlesztőink párhuzamosan dolgoznak a platform több verzióján, és a közbenső fájlok százai néha nehézségeket okoznak a munkában. A probléma kellemetlen, de nem kritikus, a megoldását egyelőre halasztottuk. Egy lehetséges megoldásként a (például a Google ezt használja a Chrome böngésző fejlesztése során).
Forrás: habr.com
