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

Prije nekog vremena (u jesen 2016.), tijekom razvoja sljedeće verzije tehnološke platforme 1C:Enterprise, postavilo se pitanje unutar razvojnog tima o podršci novom standardu C ++ 14 u našem kodu. Prijelaz na novi standard, kao što smo i pretpostavili, omogućio bi nam da mnoge stvari napišemo elegantnije, jednostavnije i pouzdanije, te bi pojednostavio podršku i održavanje koda. Čini se da u prijevodu nema ničeg izvanrednog, osim veličine baze koda i specifičnih značajki našeg koda.

Za one koji ne znaju, 1C:Enterprise je okruženje za brzi razvoj višeplatformskih poslovnih aplikacija i runtime za njihovo izvršavanje na različitim operativnim sustavima i DBMS-ovima. Općenito, proizvod sadrži:

Trudimo se pisati isti kod za različite operativne sustave što je više moguće - baza koda poslužitelja je 99% uobičajena, baza koda klijenta je oko 95%. Tehnološka platforma 1C:Enterprise primarno je napisana u jeziku C++, a približne karakteristike koda navedene su u nastavku:

  • 10 milijuna linija C++ koda,
  • 14 tisuća datoteka,
  • 60 tisuća razreda,
  • pola milijuna metoda.

I sve te stvari su morale biti prevedene u C++14. Danas ćemo vam reći kako smo to učinili i na što smo naišli u tom procesu.

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

Odricanje

Sve dolje napisano o sporom/brzom radu, (ne)velikoj potrošnji memorije implementacijama standardnih klasa u raznim bibliotekama znači jedno: ovo vrijedi ZA NAS. Vrlo je moguće da će standardne implementacije biti najprikladnije za vaše zadatke. Krenuli smo od vlastitih zadataka: uzeli smo podatke tipične za naše klijente, pokrenuli tipične scenarije na njima, pogledali performanse, količinu potrošene memorije itd., te analizirali jesmo li mi i naši klijenti zadovoljni takvim rezultatima ili ne . I djelovali su ovisno o.

Što smo imali

U početku smo napisali kod za platformu 1C:Enterprise 8 koristeći 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. Ali kod je napisan prema standardu iz 1998. i, na primjer, naše desne kutne zagrade bile su odvojene razmacima kako bi kompilacija uspjela, ovako:

vector<vector<int> > IntV;

Godine 2006., izdavanjem verzije platforme 8.1, počeli smo podržavati Linux i prebacili se na standardnu ​​biblioteku treće strane STLPort. Jedan od razloga prijelaza bio je rad sa širokim linijama. U našem kodu cijelo vrijeme koristimo std::wstring koji se temelji na tipu wchar_t. Njegova veličina u Windowsima je 2 bajta, au Linuxu zadana vrijednost je 4 bajta. To je dovelo do nekompatibilnosti naših binarnih protokola između klijenta i poslužitelja, kao i raznih trajnih podataka. Koristeći gcc opcije, možete odrediti da veličina wchar_t tijekom kompilacije također bude 2 bajta, ali tada možete zaboraviti na korištenje standardne biblioteke iz kompajlera, jer koristi glibc, koji je pak kompajliran za 4-bajtni wchar_t. Ostali razlozi bili su bolja implementacija standardnih klasa, podrška za hash tablice, pa čak i emulacija semantike kretanja unutar kontejnera, što smo aktivno koristili. I još jedan razlog, kako se kaže posljednji, ali ne i najmanje važan, bila je izvedba žica. Imali smo svoj sat za gudače, jer... Zbog specifičnosti našeg softvera, string operacije se koriste vrlo široko i za nas je to kritično.

Naš niz temelji se na idejama optimizacije niza iznesenim u ranim 2000-ima Andrej Aleksandresku. Kasnije, kada je Alexandrescu radio u Facebooku, na njegov prijedlog, u pogonu Facebooka korištena je linija koja je radila na sličnim principima (vidi biblioteku glupost).

Naša linija koristila je dvije glavne tehnologije optimizacije:

  1. Za kratke vrijednosti koristi se interni međuspremnik u samom objektu niza (ne zahtijeva dodatnu dodjelu memorije).
  2. Za sve ostale koristi se mehanika Kopiraj pri pisanju. Vrijednost niza je pohranjena na jednom mjestu, a referentni brojač se koristi tijekom dodjele/modifikacije.

Kako bismo ubrzali kompilaciju platforme, isključili smo implementaciju streama iz naše STLPort varijante (koju nismo koristili), što nam je dalo oko 20% bržu kompilaciju. Nakon toga morali smo ga ograničeno koristiti Pojačati. Boost intenzivno koristi stream, posebno u svojim API-jima usluga (na primjer, za bilježenje), pa smo ga morali modificirati kako bismo uklonili upotrebu streama. To nam je pak otežalo prelazak na nove verzije Boosta.

Treći put

Prilikom prelaska na standard C++14 razmotrili smo sljedeće mogućnosti:

  1. Nadogradite STLPort koji smo modificirali na standard C++14. Opcija je vrlo teška, jer... podrška za STLPort ukinuta je 2010. i morali bismo sami izgraditi sav njegov kod.
  2. Prijelaz na drugu STL implementaciju kompatibilnu sa C++14. Vrlo je poželjno da ova implementacija bude za Windows i Linux.
  3. Prilikom prevođenja za svaki OS, koristite biblioteku ugrađenu u odgovarajući prevodilac.

Prva opcija je odmah odbijena zbog previše posla.

Neko smo vrijeme razmišljali o drugoj opciji; smatrati kandidatom libc++, ali u to vrijeme nije radio pod Windowsima. Da biste prenijeli libc++ na Windows, morali biste obaviti puno posla - na primjer, sami napisati sve što ima veze s nitima, sinkronizacijom niti i atomičnosti, budući da se libc++ koristi u ovim područjima POSIX API.

A mi smo izabrali treći put.

Tranzicija

Dakle, morali smo zamijeniti korištenje STLPorta bibliotekama odgovarajućih kompilatora (Visual Studio 2015 za Windows, gcc 7 za Linux, clang 8 za macOS).

Srećom, naš kod je napisan uglavnom prema smjernicama i nije koristio kojekakve pametne trikove, tako da je migracija na nove biblioteke tekla relativno glatko, uz pomoć skripti koje su zamijenile nazive tipova, klasa, imenskih prostora i uključivanja u izvor. datoteke. Migracija je zahvatila 10 000 izvornih datoteka (od 14 000). wchar_t je zamijenjen sa char16_t; odlučili smo napustiti korištenje wchar_t, jer char16_t zauzima 2 bajta na svim operativnim sustavima i ne kvari kompatibilnost koda između Windowsa i Linuxa.

Bilo je tu malih dogodovština. Na primjer, u STLPortu iterator se može implicitno pretvoriti u pokazivač na element, a na nekim mjestima u našem kodu to je korišteno. U novim knjižnicama to više nije bilo moguće učiniti, te su se ti odlomci morali analizirati i prepisivati ​​ručno.

Dakle, migracija koda je završena, kod je kompiliran za sve operativne sustave. Vrijeme je za testove.

Testovi nakon prijelaza pokazali su pad performansi (na nekim mjestima i do 20-30%) i povećanje potrošnje memorije (do 10-15%) u odnosu na staru verziju koda. To je posebno zbog suboptimalne izvedbe standardnih žica. Stoga smo ponovno morali koristiti vlastitu, malo modificiranu liniju.

Također je otkrivena zanimljiva značajka implementacije spremnika u ugrađenim bibliotekama: prazni (bez elemenata) std::map i std::set iz ugrađenih biblioteka dodjeljuju memoriju. A zbog značajki implementacije, na nekim mjestima u kodu stvara se dosta praznih spremnika ove vrste. Standardni memorijski spremnici raspoređeni su malo, za jedan korijenski element, ali za nas se to pokazalo kritičnim - u nizu scenarija, naše performanse su značajno pale, a potrošnja memorije povećana (u usporedbi sa STLPortom). Stoga smo u našem kodu ove dvije vrste spremnika iz ugrađenih biblioteka zamijenili njihovom implementacijom iz Boosta, gdje ovi spremnici nisu imali tu značajku, a to je riješilo problem s usporavanjem i povećanom potrošnjom memorije.

Kao što se često događa nakon velikih promjena u velikim projektima, prva iteracija izvornog koda nije radila bez problema, a ovdje je posebno dobro došla podrška za debugging iteratore u Windows implementaciji. Korak po korak napredovali smo i do proljeća 2017. (verzija 8.3.11 1C:Enterprise) migracija je završena.

Rezultati

Prijelaz na C++14 standard trajao nam je oko 6 mjeseci. Većinu vremena na projektu je radio jedan (ali vrlo visoko kvalificiran) programer, au završnoj fazi pridružili su se predstavnici timova zaduženih za pojedina područja - UI, klaster poslužitelja, razvojni i administrativni alati itd.

Prijelaz je uvelike pojednostavio naš rad na prelasku na najnovije verzije standarda. Tako je verzija 1C:Enterprise 8.3.14 (u razvoju, izlazak planiran za početak sljedeće godine) već prebačen na standard C++17.

Nakon migracije programeri imaju više opcija. Ako smo ranije imali vlastitu modificiranu verziju STL-a i jedan std imenski prostor, sada imamo standardne klase iz ugrađenih biblioteka prevoditelja u std imenskom prostoru, u stdx imenskom prostoru - naše linije i spremnike optimizirane za naše zadatke, u boostu - najnovija verzija boosta. A programer koristi one klase koje su optimalno prikladne za rješavanje njegovih problema.

"Nativna" implementacija konstruktora poteza također pomaže u razvoju (premjestiti konstruktore) za određeni broj klasa. Ako klasa ima konstruktor pomicanja i ta je klasa smještena u spremnik, tada STL optimizira kopiranje elemenata unutar spremnika (na primjer, kada je spremnik proširen i potrebno je promijeniti kapacitet i preraspodijeliti memoriju).

Letite u masti

Možda je najneugodnija (ali ne i kritična) posljedica migracije to što smo suočeni s povećanjem volumena obj datoteke, a puni rezultat izgradnje sa svim posrednim datotekama počeo je zauzimati 60–70 GB. Ovo ponašanje je zbog osobitosti modernih standardnih biblioteka, koje su postale manje kritične prema veličini generiranih servisnih datoteka. To ne utječe na rad kompajlirane aplikacije, ali uzrokuje niz neugodnosti u razvoju, posebno povećava vrijeme kompilacije. Zahtjevi za slobodnim prostorom na disku na poslužiteljima za izgradnju i na strojevima za programere također se povećavaju. Naši programeri rade paralelno na nekoliko verzija platforme, a stotine gigabajta međudatoteka ponekad im stvaraju poteškoće u radu. Problem je neugodan, ali nije kritičan, za sada smo odgodili njegovo rješavanje. Razmatramo tehnologiju kao jednu od mogućnosti rješenja jedinstvo izgraditi (konkretno, Google ga koristi pri razvoju preglednika Chrome).

Izvor: www.habr.com

Dodajte komentar