Si e kemi përkthyer 10 milionë rreshta kodi C++ në standardin C++14 (dhe më pas në C++17)

Dikur (në vjeshtën e vitit 2016), gjatë zhvillimit të një versioni të ri të platformës teknologjike 1C:Enterprise, ekipi i zhvillimit u ballafaqua me çështjen e mbështetjes së standardit të ri C++14 në kodin tonë. Kalimi në standardin e ri, siç e parashikuam, do të na lejonte të shkruanim shumë gjëra më eleganta, më të thjeshta dhe më të besueshme, duke e thjeshtuar mbështetje dhe mirëmbajtjen e kodit. Duket se nuk ka asgjë të jashtëzakonshme në përkthim, përveç përmasave të kodit dhe veçorive specifike të kodit tonë.

Për ata që nuk e dinë, 1C:Enterprise është një mjedis për zhvillimin e shpejtë të aplikacioneve biznesore ndër-platformë dhe runtime për ekzekutimin e tyre në sisteme të ndryshme operative dhe DBMS. Në përmbledhje, produkti përfshin:

  • NjĂ« grup serverash aplikacionesh, punon nĂ« Windows dhe Linux
  • Klienti, qĂ« komunikon me serverin pĂ«rmes http(s) ose me protokollin tonĂ« binar, punon nĂ« Windows, Linux, macOS
  • Klienti web, qĂ« funksionon nĂ« shfletuesit Chrome, Internet Explorer, Microsoft Edge, Firefox, Safari (shkruar nĂ« JavaScript)
  • Mjedisi i zhvillimit (Konfiguratori), punon nĂ« Windows, Linux, macOS
  • Mjetet e administratĂ«s tĂ« serverave tĂ« aplikacioneve, punojnĂ« nĂ« Windows, Linux, macOS
  • Klienti mobil, qĂ« lidhet me serverin pĂ«rmes http(s), punon nĂ« pajisje mobile me sistemin operativ Android, iOS, Windows
  • Platforma mobil — njĂ« kornizĂ« pĂ«r krijimin e aplikacioneve mobile offline me mundĂ«si sinkronizimi, punon nĂ« Android, iOS, Windows
  • Mjedisi i zhvillimit 1C:Enterprise Development Tools, shkruar nĂ« Java
  • ХДрĐČДр Sistemet e NdĂ«rveprimit

Ne pĂ«rpiqemi tĂ« shkruajmĂ« njĂ« kod tĂ« vetĂ«m pĂ«r sisteme tĂ« ndryshme operative — baza e kodit tĂ« serverit Ă«shtĂ« e pĂ«rbashkĂ«t nĂ« 99%, e klientit — rreth 95%. Platforma teknologjike 1C:Enterprise Ă«shtĂ« kryesisht e shkruar nĂ« C++ dhe mĂ« poshtĂ« janĂ« karakteristikat pĂ«rkatĂ«se tĂ« kodit:

  • 10 milion rreshta kodi C++,
  • 14 mijĂ« skedarĂ«,
  • 60 mijĂ« klasa,
  • njĂ« gjysmĂ« milioni metoda.

Dhe të gjithë këto gjëra duhej të përfitoheshin në C++14. Ne do të tregojmë sot se si e bëmë këtë dhe me çfarë sfidash u përballëm në proces.

Si e kemi përkthyer 10 milionë rreshta kodi C++ në standardin C++14 (dhe më pas në C++17)

Kufizimi

Çdo gjĂ« e shkruar mĂ« poshtĂ« rreth punĂ«s sĂ« ngadaltĂ«/shpejtĂ«, (jo) konsumi i vogĂ«l tĂ« memories nga implementimet e klasave standarde nĂ« biblioteka tĂ« ndryshme Ă«shtĂ« e vĂ«rtetĂ«: kjo Ă«shtĂ« e saktĂ« PËR NE. Mund tĂ« jetĂ« qĂ« pĂ«r detyrat tuaja implementimet standarde tĂ« ndihmojnĂ« mĂ« sĂ« miri. Ne u bazuam nĂ« nevojat tona: morĂ«m tĂ« dhĂ«nat tipike pĂ«r klientĂ«t tanĂ«, i testuam nĂ« skenarĂ« tipikĂ«, vlerĂ«suam shpejtĂ«sinĂ« e punĂ«s, sasinĂ« e memories sĂ« konsumuar, etj., dhe analizuam – a na kĂ«naqin rezultatat e tilla ne dhe klientĂ«t tanĂ« apo jo. Dhe vepruam pĂ«rkatĂ«sisht.

ÇfarĂ« kishim

Fillimisht, ne shkruam kodin e platformës 1C:Enterprise 8 në Microsoft Visual Studio. Projekti filloi në fillim të viteve 2000 dhe ne kishim vetëm një version për Windows. Natyrisht, që atëherë kodi u zhvillua aktivisht, shumë mekanizma u rishkruan plotësisht. Por kodi ishte shkruar sipas standardit të vitit 1998, dhe për shembull, këndet e djathta ishin ndarë me hapësira për të kaluar me sukses kompilimin, kështu:

vector<vector > IntV;

Në vitin 2006, me daljen e versionit 8.1 të platformës, ne filluam të mbështesim Linux dhe kaluam në një bibliotekë standarde të jashtme STLPort. Një nga arsyet për kalimin ishte puna me vargje të gjera. Ne përdorim gjithashtu std::wstring, i bazuar në tipin wchar_t. Madhësia e tij në Windows është 2 byte, ndërsa në Linux për default është 4 byte. Kjo çoi në papajtueshmëri të protokolleve tona binarë midis klientit dhe serverit, si dhe të dhënave të ndryshme të qëndrueshme. Me opsionet e gcc mund të specifikohet që madhësia e wchar_t gjatë kompilimit të jetë gjithashtu 2 byte, por atëherë harrojmë për përdorimin e bibliotekës standarde nga kompajleri, pasi ajo përdor glibc, e cila është e kompajluar për wchar_t 4-byte. Arsyet e tjera ishin implementimi më cilësor i klasave standarde, mbështetje e tabelave të hash dhe madje edhe imitim i semantikës së lëvizjes brenda konteinerëve, të cilat ne i përdorim aktivisht. Dhe një arsye tjetër, siç thuhet është e fundit por jo më pak e rëndësishme, ishte performanca e vargjeve. Ne kishim klasën tonë për vargje, pasi për shkak të natyrës së softuerit tonë, operacionet mbi vargje përdoren shumë gjerësisht dhe për ne kjo është kritike.

Vargu ynë është i bazuar në idetë e optimizimit të vargjeve, të paraqitura në fillim të viteve 2000 Andrei Alexandrescu. Më vonë, kur Alexandrescu punonte në Facebook, në iniciativën e tij në motorin e Facebook u përdor një varg që funksiononte sipas parimeve të ngjashme (shih bibliotekën folly).

. Në vargun tonë ishin përdorur dy teknologji kryesore optimizimi:

  1. Për vlera të shkurtra, përdoret një tampon i brendshëm në objektin e vargut (që nuk kërkon alokim të shtesë të memories).
  2. Për të gjitha të tjerat përdoret mekanika Copy On Write. Vlera e vargut ruhet në një vend, kur i atribuohet/modifikohet përdoret numri i referencave.

Për të përshpejtuar kompilimin e platformës, ne përjashtuam nga versioni ynë STLPort implementimin e stream (të cilin nuk e përdorim), çka na dha një përshpejtim të kompilimit afërsisht 20%. Më vonë, na duhej të përdornim me kufizime. Boost. Boost përdor aktivisht stream, sidomos në API-të e tij të shërbimeve (p.sh. për logimin), prandaj na duhej të modifikonim atë, duke përjashtuar përdorimin e stream. Kjo, nga ana tjetër, e bëri të vështirë kalimin në versionet e reja të Boost.

Rruga e tretë

Kur kaluam në standardin C++14, shqyrtuam këto mundësi:

  1. Të çonim STLPortin e modifikuar nga ana jonë në standardin C++14. Kjo opsion ishte shumë e vështirë, pasi mbështetja për STLPort ishte ndalur në vitin 2010, dhe na duhej ta ngrisnim të gjithë kodin vetë.
  2. Të kalojmë në një implementim tjetër të STL, të kompatibilshëm me C++14. Preferohej që ky implementim të ishte për Windows dhe Linux.
  3. Të përdorim bibliotekën e integruar të përkatshëm për çdo OS gjatë kompilimit.

Opsioni i parë u refuzua menjëherë për shkak të volumit të madh të punës.

NĂ« fillim menduam pĂ«r opsionin e dytĂ«; si kandidat kishim libc++, por nĂ« atĂ« kohĂ« nuk punonte pĂ«r Windows. PĂ«r tĂ« portuar libc++ nĂ« Windows, do tĂ« duhej tĂ« bĂ«nim shumĂ« punĂ« — pĂ«r shembull, tĂ« shkruanim vetĂ« gjithçka qĂ« lidhet me rrjedhat, sinkronizimin e rrjedhave dhe atomikĂ«n, pasi nĂ« libc++ ishin pĂ«rdorur POSIX API.

Dhe zgjodhëm rrugën e tretë.

Kalimi

Pra, na duhej të zëvendësonim përdorimin e STLPort me bibliotekat përkatëse të kompilerëve (Visual Studio 2015 për Windows, gcc 7 për Linux, clang 8 për macOS).

Fatmirësisht, kodi ynë ishte kryesisht i shkruar sipas udhëzimeve dhe nuk përdorte truket e ndryshme, kështu që migraimi në bibliotekat e reja kaloi mjaft pa probleme, duke përdorur skriptet që zëvendësuan në skedarët e burimit emrat e tipave, klasave, hapësirave të emrave dhe përfshirjeve. Migrimi përfshiu 10,000 skedarë burimorë (nga 14,000). wchar_t u zëvendësua me char16_t; ne vendosëm të hiqnim përdorimin e wchar_t, pasi char16_t në të gjitha OS-të merr 2 byte dhe nuk prish kompatibilitetin e kodit midis Windows dhe Linux.

Nuk kaluam pa aventura të vogla. Për shembull, në STLPort një iterator mund të konvertohej në mënyrë të pakët në një tregues për elementin, dhe në disa vende në kodin tonë kjo ishte përdorur. Në bibliotekat e reja nuk ishte më e mundur të bëhej kështu, dhe këto vende duhej të analizohej dhe të ri-shkruheshin manualisht.

Pra, migrimi i kodit përfundoi, kodi kompilon për të gjitha OS-të. Ka ardhur koha për testet.

Testet pas kalimit treguan një rënie të performancës (në disa raste deri në 20-30%) dhe rritje të konsumit të memories (deri në 10-15%) krahasuar me versionin e vjetër të kodit. Kjo ishte, përveç të tjerash, e lidhur me punën jo optimale të vargjeve standarde. Prandaj na duhej sërish të përdornim vargje të tona, të përmirësuara pak.

Gjithashtu u zbulua një karakteristikë interesante e implementimit të kontejnerëve në bibliotekat e integruara: std::map dhe std::set të zbrazët (pa elemente) nga bibliotekat e integruara alokojnë memorie. Dhe në disa vende në kodin tonë krijohej një numër mjaft i madh kontejnerësh të tilla. Standardet e kontejnerëve alokojnë pak memorie, për një element rrënjë, por për ne kjo u bë kritike - në disa skenarë kemi vënë re një rënie të ndjeshme të performancës dhe një rritje të konsumit të memories (krahasuar me STLPort). Prandaj ne zëvendësuam në kodin tonë këto dy tipe kontejnerësh nga bibliotekat e integruara me implementimin e tyre nga Boost, ku këto kontejnerë nuk kishin këtë karakteristikë dhe kjo zgjidhi problemin me ngadalësimin dhe rritjen e konsumit të memories.

Siç ndodh shpesh pas ndryshimeve masive në projekte të mëdha, iteracioni i parë i burimeve nuk funksionoi pa probleme, dhe këtu na ndihmoi shumë, në veçanti, mbështetja për iteratorët për-debug në implementimin e Windows. Hap pas hapi ne po lëviznim përpara, dhe në pranverën e vitit 2017 (versioni 8.3.11 të 1C:Enterprise) migraimi përfundoi.

Përfundimet

Kalimi në standardin C++14 na mori rreth 6 muaj. Kryesisht një zhvillues (por shumë i kualifikuar) punoi mbi projektin, dhe në fazën përfundimtare u përfshinë përfaqësues të ekipeve që ishin përgjegjës për fusha të caktuara - UI, serverin e klasterëve, mjetet e zhvillimit dhe administratës, etj.

Kalimi e thjeshtoi shumë punën tonë për migrimin në versionet më të fundit të standardit. Në këtë mënyrë, versioni 1C:Enterprise 8.3.14 (në zhvillim, publikimi është planifikuar për fillimin e vitit të ardhshëm) tashmë është përkthyer në standard C++17.

Pas migros, zhvilluesit fituan mĂ« shumĂ« mundĂ«si. NĂ«se mĂ« parĂ« kishim versionin tonĂ« tĂ« pĂ«rmirĂ«suar tĂ« STL dhe njĂ« hapĂ«sirĂ« emrave std, tani hapĂ«sira jonĂ« e emrave std pĂ«rmban klasat standarde nga bibliotekat e integruara tĂ« kompilatorit, nĂ« hapĂ«sirĂ«n e emrave stdx – klasat tona tĂ« optimizuara pĂ«r ne, si dhe vargje dhe kontejnerĂ«, ndĂ«rsa nĂ« boost Ă«shtĂ« versioni mĂ« i ri i boost. Dhe zhvilluesi pĂ«rdor ato klasa, tĂ« cilat pĂ«rputhen mĂ« mirĂ« me zgjidhjen e problemeve tĂ« tij.

Po ashtu ndihmon edhe implementimi 'i natyrshëm' i konstruktorëve të lëvizjes (move constructors) për disa klasa. Nëse një klasë ka një konstruktor lëvizjeje dhe kjo klasë vihet në një kontejner, STL optimizon kopjimin e elementeve brenda kontejnerit (p.sh., kur kontejneri zgjerohet dhe duhet të ndryshojë kapacitetin dhe të rialokojë memorjen).

Një lugë katran

Mund tĂ« jetĂ«, ndoshta, pasoja mĂ« e pakĂ«ndshme (por jo fatale) e migros — u pĂ«rballĂ«m me rritjen e volumit obj-file, dhe rezultati pĂ«rfundimtar i ndĂ«rtimit me tĂ« gjitha skedarĂ«t e ndĂ«rmjetĂ«m tani zĂ« 60 – 70 GB. Ky sjellje Ă«shtĂ« e lidhur me karakteristikat e bibliotekave standarde moderne, tĂ« cilat janĂ« bĂ«rĂ« mĂ« pak kritike pĂ«r volumin e skedarĂ«ve shĂ«rbyes qĂ« gjenerohen. Kjo nuk ndikon nĂ« funksionimin e aplikacionit tĂ« kompiluar, por krijon disa shqetĂ«sime nĂ« zhvillim, veçanĂ«risht rrit kohĂ«n e kompilimit. KĂ«rkesat pĂ«r hapĂ«sirĂ«n e lirĂ« nĂ« disqe nĂ« serverĂ«t e ndĂ«rtimit dhe nĂ« makinat e zhvilluesve po ashtu janĂ« rritur. Zhvilluesit tanĂ« punojnĂ« paralelisht mbi disa versionet e platformĂ«s, dhe qindra gigabajt skedarĂ«sh tĂ« ndĂ«rmjetĂ«m ndonjĂ«herĂ« krijojnĂ« vĂ«shtirĂ«si nĂ« punĂ«. Problemi Ă«shtĂ« i pakĂ«ndshĂ«m, por jo kritik, ndihmĂ«n pĂ«r tĂ« zgjidhur atĂ« e kemi shtyrĂ« pĂ«r momentin. NjĂ« nga opsionet e zgjidhjes sĂ« saj po shqyrtojmĂ« Ă«shtĂ« teknika unity build (e cila, veçanĂ«risht, pĂ«rdoret nga Google gjatĂ« zhvillimit tĂ« shfletuesit Chrome).

Burimi: habr.com

Bleni hostim tĂ« besueshĂ«m pĂ«r faqe me mbrojtje nga DDoS, serverĂ« VPS VDS đŸ”„ Bleni hostim tĂ« besueshĂ«m pĂ«r faqe me mbrojtje nga DDoS, serverĂ« VPS VDS | ProHoster