Kako smo preveli 10 miliona linija C++ koda u C++14 standard (a zatim u C++17)

Prije nekog vremena (u jesen 2016.), tokom razvoja sljedeće verzije tehnološke platforme 1C:Enterprise, u razvojnom timu se pojavilo pitanje podrške novom standardu. C ++ 14 u našem kodu. Prelazak na novi standard, kao što smo pretpostavili, omogućio bi nam da mnoge stvari napišemo elegantnije, jednostavnije i pouzdanije, te bi pojednostavio podršku i održavanje koda. I čini se da u prijevodu nema ničeg izvanrednog, ako ne i zbog obima baze koda i specifičnih karakteristika našeg koda.

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

Trudimo se da napišemo isti kod za različite operativne sisteme što je više moguće - baza serverskog koda je 99% uobičajena, baza koda klijenta je oko 95%. Tehnološka platforma 1C:Enterprise prvenstveno je napisana na C++, a približne karakteristike koda su date u nastavku:

  • 10 miliona linija C++ koda,
  • 14 hiljada fajlova,
  • 60 hiljada časova,
  • pola miliona metoda.

I sve ove stvari su morale biti prevedene u C++14. Danas ćemo vam reći kako smo to uradili i na šta smo se susreli u tom procesu.

Kako smo preveli 10 miliona linija C++ koda u C++14 standard (a zatim u C++17)

Odricanje od odgovornosti

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

Šta smo imali

U početku smo pisali kod za platformu 1C:Enterprise 8 koristeći Microsoft Visual Studio. Projekat je započeo ranih 2000-ih i imali smo verziju samo za Windows. Naravno, od tada se kod aktivno razvija, mnogi mehanizmi su potpuno prepisani. Ali kod je napisan prema standardu iz 1998. i, na primjer, naše pravokutne zagrade su bile razdvojene razmacima da bi kompilacija uspjela, ovako:

vector<vector<int> > IntV;

2006. godine, sa izdavanjem platforme verzije 8.1, počeli smo podržavati Linux i prešli na standardnu ​​biblioteku treće strane STLPort. Jedan od razloga tranzicije bio je rad sa širokim linijama. U našem kodu koristimo std::wstring, koji je baziran na tipu wchar_t. Njegova veličina u Windows-u je 2 bajta, au Linux-u zadana vrijednost je 4 bajta. To je dovelo do nekompatibilnosti naših binarnih protokola između klijenta i servera, kao i do raznih trajnih podataka. Koristeći gcc opcije, možete odrediti da je veličina wchar_t tokom kompilacije također 2 bajta, ali tada možete zaboraviti na korištenje standardne biblioteke iz kompajlera, jer koristi glibc, koji je zauzvrat kompajliran za 4-bajtni wchar_t. Drugi 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 kažu na kraju, ali ne i najmanje važno, bila je izvedba žica. Imali smo svoj čas za gudače, jer... Zbog specifičnosti našeg softvera, string operacije se koriste veoma široko i za nas je to kritično.

Naš niz je baziran na idejama optimizacije stringova izraženim još ranih 2000-ih Andrei Alexandrescu. Kasnije, kada je Alexandrescu radio u Facebooku, na njegov prijedlog, korištena je linija u Facebook motoru 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 bafer u samom objektu niza (ne zahtijeva dodatnu dodjelu memorije).
  2. Za sve ostale koristi se mehanika Copy On Write. Vrijednost stringa se pohranjuje na jednom mjestu, a referentni brojač se koristi tokom dodjele/modifikacije.

Da 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 smo morali da koristimo ograničeno pojačati. Boost intenzivno koristi stream, posebno u svojim API-jima za usluge (na primjer, za evidentiranje), pa smo morali da ga modificiramo kako bismo uklonili upotrebu streama. To nam je zauzvrat otežalo prelazak na nove verzije Boosta.

Treći način

Prilikom prelaska na standard C++14, razmotrili smo sljedeće opcije:

  1. Nadogradite STLPort koji smo modificirali na C++14 standard. Opcija je veoma teška, jer... Podrška za STLPort je ukinuta 2010. godine i morali bismo sami da napravimo sav njegov kod.
  2. Prelazak na drugu STL implementaciju kompatibilnu sa C++14. Veoma je poželjno da ova implementacija bude za Windows i Linux.
  3. Prilikom kompajliranja za svaki OS, koristite biblioteku ugrađenu u odgovarajući kompajler.

Prva opcija je potpuno odbijena zbog previše posla.

O drugoj opciji razmišljali smo neko vrijeme; smatra se kandidatom libc++, ali u to vrijeme nije radio pod Windowsom. Da biste prenijeli libc++ na Windows, morali biste puno raditi - na primjer, sami napišite sve što ima veze s nitima, sinhronizacijom niti i atomičnosti, budući da se libc++ koristi u ovim područjima POSIX API.

I mi smo izabrali treći put.

Tranzicija

Dakle, morali smo da zamenimo upotrebu STLPort-a sa bibliotekama odgovarajućih kompajlera (Visual Studio 2015 za Windows, gcc 7 za Linux, clang 8 za macOS).

Srećom, naš kod je pisan uglavnom prema smjernicama i nije koristio svakakve pametne trikove, pa je migracija na nove biblioteke tekla relativno glatko, uz pomoć skripti koje su zamijenile nazive tipova, klasa, imenskih prostora i uključile u izvorni datoteke. Migracija je uticala na 10 izvornih datoteka (od 000). wchar_t je zamijenjen sa char14_t; odlučili smo da napustimo upotrebu wchar_t, jer char000_t zauzima 16 bajta na svim operativnim sistemima i ne kvari kompatibilnost koda između Windowsa i Linuxa.

Bilo je nekoliko malih avantura. Na primjer, u STLPort-u iterator bi se mogao implicitno prebaciti na pokazivač na element, a na nekim mjestima u našem kodu se to koristilo. U novim bibliotekama to više nije bilo moguće učiniti, te su odlomci morali biti analizirani i prepisani ručno.

Dakle, migracija koda je završena, kod je kompajliran za sve operativne sisteme. Vrijeme je za testove.

Testovi nakon tranzicije 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 bilo zbog neoptimalnih performansi standardnih žica. Stoga smo opet morali koristiti vlastitu, malo izmijenjenu liniju.

Otkrivena je i zanimljiva karakteristika implementacije kontejnera u ugrađene biblioteke: prazan (bez elemenata) std::map i std::set iz ugrađenih biblioteka dodeljuju memoriju. A zbog karakteristika implementacije, na nekim mjestima u kodu se stvara dosta praznih kontejnera ovog tipa. Standardni memorijski kontejneri su dodijeljeni malo, za jedan korijenski element, ali za nas se to pokazalo kritičnim - u nizu scenarija naše performanse su značajno pale i potrošnja memorije se povećala (u poređenju sa STLPort-om). Stoga smo u našem kodu zamijenili ova dva tipa kontejnera iz ugrađenih biblioteka njihovom implementacijom iz Boosta, gdje ovi kontejneri nisu imali ovu mogućnost, a time je riješen problem usporavanja i povećane potrošnje 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 otklanjanje grešaka iteratora u Windows implementaciji. Korak po korak išli smo naprijed i do proljeća 2017. (verzija 8.3.11 1C:Enterprise) migracija je završena.

Ishodi

Prelazak na C++14 standard nam je trajao oko 6 mjeseci. Većinu vremena na projektu je radio jedan (ali vrlo kvalifikovan) developer, a u završnoj fazi su se uključili predstavnici timova odgovornih za određene oblasti - UI, serverski klaster, razvojni i administrativni alati itd.

Tranzicija je uvelike pojednostavila naš rad na prelasku na najnovije verzije standarda. Tako je verzija 1C:Enterprise 8.3.14 (u razvoju, izdanje zakazano za početak sljedeće godine) već prebačena 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 kompajlerskih biblioteka u std imenskom prostoru, u stdx imenskom prostoru - naše linije i kontejneri optimizirani 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 pokreta također pomaže u razvoju (konstruktori pomeranja) za više časova. Ako klasa ima konstruktor premještanja i ova klasa je smještena u kontejner, tada STL optimizira kopiranje elemenata unutar kontejnera (na primjer, kada se kontejner proširi i potrebno je promijeniti kapacitet i preusmjeriti memoriju).

Leteti u masti

Možda je najneugodnija (ali ne i kritična) posljedica migracije to što smo suočeni s povećanjem obima obj datoteke, a potpuni rezultat gradnje sa svim srednjim datotekama počeo je zauzimati 60–70 GB. Ovo ponašanje je zbog posebnosti modernih standardnih biblioteka, koje su postale manje kritične prema veličini generiranih servisnih datoteka. Ovo ne utiče na rad kompajlirane aplikacije, ali uzrokuje niz neugodnosti u razvoju, posebno povećava vrijeme kompilacije. Zahtjevi za slobodnim prostorom na disku na serverima za izgradnju i na mašinama za programere također se povećavaju. Naši programeri rade na nekoliko verzija platforme paralelno, a stotine gigabajta međufajlova ponekad stvaraju poteškoće u njihovom radu. Problem je neprijatan, ali nije kritičan, za sada smo odgodili njegovo rješavanje. Tehnologiju razmatramo kao jednu od opcija za njeno rješavanje izgraditi jedinstvo (Google ga posebno koristi kada razvija Chrome pretraživač).

izvor: www.habr.com

Dodajte komentar