Kako smo preveli 10 milijuna redaka C++ koda na standard C++14 (a potom i na C++17)

Nekoliko vremena unazad (jesen 2016), prilikom razvoja nove verzije tehnološke platforme 1C:Poduzeće, unutar tima za razvoj postavilo se pitanje podrške novom standardu C++14 u našem kodu. Prelazak na novi standard, kao što smo pretpostavljali, omogućio bi nam da pišemo mnoge stvari elegantnije, jednostavnije i pouzdanije, olakšavajući podršku i održavanje koda. A u prevodu, kao da ne postoji ništa neobično, da nije razmere kodne baze i specifičnih karakteristika našeg koda.

Za one koji ne znaju, 1C:Poduzeće – to je okruženje za brzi razvoj multiplatformskih poslovnih aplikacija i runtime za njihovo izvršavanje na različitim operativnim sistemima i DBMS. U opštem smislu, proizvoda uključuje:

Trudimo se maksimalno da napišemo jedan kod za različite OS – kodna baza servera je zajednička u 99%, klijent – otprilike 95%. Tehnološka platforma 1C:Poduzeće većinom je napisana na C++ i u nastavku su priblžne karakteristike koda:

  • 10 miliona redova C++ koda,
  • 14 hiljada datoteka,
  • 60 hiljada klasa,
  • pol miliona metoda.

I sve to imanje trebalo je prevesti na C++14. O tome kako smo to radili i s čim smo se suočili tokom procesa, danas ćemo govoriti.

Kako smo preveli 10 milijuna redaka C++ koda na standard C++14 (a potom i na C++17)

Odricanje

Sve što je napisano u nastavku o sporom/brzom radu i (ne)velikom potrošnjom memorije standardnih klasa u različitim bibliotekama znači jedno: to se odnosi na NAS. Moguće je da će standardne implementacije najbolje odgovarati vašim potrebama. Mi smo se vodili našim zadacima: uzimali uobičajene podatke naših klijenata, provodili tipične scenarije, pratili performanse, potrošnju memorije itd., i analizirali – jesu li nas i naše klijente takvi rezultati zadovoljavali ili ne. I postupali smo u skladu s tim.

Što smo imali

Isprva smo pisali kod platforme 1C:Poduzeće 8 u Microsoft Visual Studio. Projekt je započeo početkom 2000-ih i imali smo verziju samo za Windows. Naravno, od tada se kod aktivno razvijao, mnogi mehanizmi su potpuno prepisani. No kod je pisan prema standardu iz 1998. godine, i, na primjer, desne uglaste zagrade bile su odvojene razmacima kako bi se kompilacija uspješno završila, ovako:

vector<vector > IntV;

Godine 2006., s izlaskom verzije platforme 8.1, počeli smo podržavati Linux i prešli na vanjsku standardnu biblioteku STLPort. Jedan od razloga prelaska bila je podrška za široke nizove. U našem kodu svuda koristimo std::wstring, koji se temelji na tipu wchar_t. Njegova veličina u Windowsu je 2 bajta, a u Linuxu po defaultu 4 bajta. To je dovodilo do nespojivosti naših binarnih protokola između klijenta i poslužitelja, kao i različitih perzistentnih podataka. Opcijama gcc može se navesti da veličina wchar_t pri kompilaciji bude također 2 bajta, ali tada se može zaboraviti na korištenje standardne biblioteke od kompajlera, budući da koristi glibc, koja je, pak, skompajlirana za 4-bajtni wchar_t. Drugi razlozi uključivali su kvalitetniju implementaciju standardnih klasa, podršku za hash tablice i čak emulaciju semantike premještanja unutar kontejnera, koju smo aktivno koristili. A još jedan razlog, kao što kažu, posljednji ali ne manje važan, bila je performansa nizova. Imali smo svoju klasu za nizove, jer se u našem softveru, zbog specifičnosti, operacije s nizovima koriste vrlo široko i to je za nas ključno.

Naš niz temelji se na idejama optimizacije nizova koje je iznio još početkom 2000-ih Andrei Alexandrescu. Kasnije, dok je Alexandrescu radio u Facebooku, na njegovu inicijativu u tražilici Facebook upotrebljena je funkcija koja radi na sličnim principima (vidi knjižnicu folly).

. U našem stringu korištene su dvije glavne tehnologije optimizacije:

  1. Za kratke vrijednosti koristi se unutarnji buferski pohrana u samom objektu stringa (ne zahtijevajući dodatnu alokaciju memorije).
  2. Za sve ostale koristi se mehanika Copy On Write. Vrijednost stringa pohranjuje se na jednom mjestu, a prilikom dodjele/modifikacije koristi se brojač referenci.

Kako bismo ubrzali kompilaciju platforme, isključili smo iz naše verzije STLPort implementaciju streama (koju nismo koristili), što nam je dalo ubrzanje kompilacije otprilike 20%. Kasnije smo ograničeno morali koristiti Boost. Boost aktivno koristi stream, posebno u svojim servisnim API-jima (na primjer, za logiranje), pa smo morali modificirati našu verziju isključujući korištenje streama. To nam je, pak, otežalo prijelaz na nove verzije Boost.

Treći put

Prilikom prijelaza na standard C++14 razmatrali smo takve opcije:

  1. Podizanje modificiranog STLPort-a na standard C++14. Opcija je vrlo složena, budući da je podrška za STLPort prekinuta 2010. godine, a sav njegov kod morali bismo podići sami.
  2. Prijelaz na drugu implementaciju STL-a, kompatibilnu s C++14. Izuzetno je poželjno da ova implementacija bude pod Windows i Linux.
  3. Korištenje prikladne biblioteke ugrađene u odgovarajući kompajler prilikom kompilacije za svaku OS.

Prva opcija odmah je odbijena zbog prevelikog obujma rada.

Nekoliko smo vremena razmišljali o drugoj opciji; kao kandidata razmatrali smo libc++, ali u to vrijeme nije radio pod Windows. Da bismo portirali libc++ na Windows, trebalo bi obaviti mnogo posla — na primjer, sami napisati sve što se tiče niti, sinkronizacije niti i atomskih operacija, budući da je libc++ u tim područjima koristio POSIX API.

I izabrali smo treći put.

Prijelaz

Dakle, morali smo zamijeniti korištenje STLPort-a s knjižnicama odgovarajućih kompajlera (Visual Studio 2015 za Windows, gcc 7 za Linux, clang 8 za macOS).

Na sreću, naš kod je uglavnom pisan prema smjernicama i nije koristio razne lukavštine, tako da je migracija na nove biblioteke prolazila relativno glatko, uz pomoć skripti koje su zamjenjivale imena tipova, klasa, prostora imena i uključivanja u izvorne datoteke. Migracija je obuhvatila 10 000 izvornika (od 14 000). wchar_t je zamijenjen sa char16_t; odlučili smo se odustati od korištenja wchar_t, jer char16_t na svim OS-ima zauzima 2 bajta i ne kvari kompatibilnost koda između Windowsa i Linuxa.

Nije prošlo bez malih avantura. Na primjer, u STLPort-u se iterator mogao implicitno castati u pokazivač na element, i na nekim mjestima našeg koda to je korišteno. U novim bibliotekama to više nije bilo moguće, pa su ta mjesta morala biti analizirana i ručno prepisana.

Dakle, migracija koda je završena, kod se kompajlira za sve OS-e. Došlo je vrijeme za testiranje.

Testovi nakon prelaska su pokazali pad performansi (na nekim mjestima do 20-30%) i povećanje potrošnje memorije (do 10-15%) u usporedbi sa starom verzijom koda. To je bilo, između ostalog, povezano s neoptimalnim radom standardnih stringova. Zato smo opet morali koristiti našu, malo doradjenu verziju stringova.

Također se pokazala zanimljiva karakteristika implementacije spremnika u ugrađenim bibliotekama: prazni (bez elemenata) std::map i std::set iz ugrađenih biblioteka alociraju memoriju. A kod nas, zbog osobitosti implementacije, u nekim dijelovima koda se stvara prilično mnogo praznih spremnika ovog tipa. Standardni spremnici alociraju malo memorije, za jedan korijenski element, ali za nas je to bilo kritično – u nekoliko scenarija smo osjetno izgubili performanse i povećali potrošnju memorije (u usporedbi sa STLPort-om). Stoga smo u našem kodu zamijenili ta dva tipa spremnika iz ugrađenih biblioteka njihovom implementacijom iz Boost-a, gdje ti spremnici nisu imali tu osobinu, i to je riješilo problem usporavanja i povećane potrošnje memorije.

Kao što to obično biva nakon opsežnih promjena u velikim projektima, prva iteracija izvora nije radila bez problema, i ovdje nam je posebno pomogla podrška od debug iteratora u Windows implementaciji. Korak po korak napredovali smo, i do proljeća 2017 (verzija 8.3.11 1C: Predmet) migracija je bila završena.

Zaključci

Prijelaz na standard C++14 trajao je oko 6 mjeseci. Veći dio vremena na projektu radio je jedan (ali vrlo kvalificirani) programer, a u završnoj fazi pridružili su se predstavnici timova odgovornih za specifična područja — UI, klaster servera, razvojna i administracijska sredstva itd.

Prijelaz nam je mnogo olakšao rad na migraciji na najnovije verzije standarda. Tako je verzija 1C:Preduzeće 8.3.14 (u razvoju, izdanje je planirano za početak sljedeće godine) već prevedena na standard C++17.

Nakon migracije programeri su dobili više mogućnosti. Ako smo ranije imali vlastitu prerađenu verziju STL i jedan naziv prostor std, sada se u nazivnom prostoru std nalaze standardne klase iz ugrađenih biblioteka kompajlera, u nazivnom prostoru stdx – naši, optimizirani za naše zadatke stringovi i kontejneri, u boost – svježa verzija boosta. Programer koristi one klase koje optimalno odgovaraju rješavanju njegovih zadataka.

Pomaže u razvoju također i 'izvorna' implementacija konstruktora premještanja (move constructors) za niz klasa. Ako klasa ima konstruktor premještanja i ta se klasa smjesti u kontejner, STL optimizira kopiranje elemenata unutar kontejnera (na primjer, kada se kontejner proširuje i treba promijeniti kapacitet i reallocirati memoriju).

Kašika katrana

Najneugodnija (ali ne kritična) posljedica migracije — susreli smo se s povećanjem volumena obj-datoteka, a konačni rezultat gradnje sa svim međufajlovima postao je 60 – 70 GB. Takvo ponašanje povezano je s karakteristikama modernih standardnih biblioteka, koje su postale manje kritične prema volumenu generiranih pomoćnih datoteka. To ne utječe na rad kompajlirane aplikacije, ali donosi određene neugodnosti u razvoju, osobito povećava vrijeme kompajlacije. Također se povećavaju zahtjevi za slobodnim prostorom na disku na build serverima i na računalima programera. Naši programeri paralelno rade na nekoliko verzija platforme, a stotine gigabajta međufajlova ponekad stvaraju poteškoće u radu. Problem je neugodan, ali nije kritičan, njegovo rješenje smo zasad odgodili. Kao jedan od mogućih načina rešenja razmatramo tehniku unity build (to je, između ostalog, koristi Google prilikom razvijanja preglednika Chrome).

Izvor: habr.com

Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere 🔥 Kupite pouzdan hosting za web stranice s DDoS zaštitom, VPS VDS servere | ProHoster