Kiel ni tradukis 10 milionojn da linioj de C++-kodo al la C++14-normo (kaj poste al C++17)

Antaŭ iom da tempo (en la aŭtuno de 2016), dum la disvolviĝo de la sekva versio de la teknologia platformo 1C:Enterprise, la demando ŝprucis ene de la disvolva teamo pri subteno de la nova normo. C ++ 14 en nia kodo. La transiro al nova normo, kiel ni supozis, permesus al ni skribi multajn aferojn pli elegante, simple kaj fidinde, kaj simpligus la subtenon kaj prizorgadon de la kodo. Kaj ŝajnas esti nenio eksterordinara en la traduko, se ne pro la skalo de la kodbazo kaj la specifaj trajtoj de nia kodo.

Por tiuj, kiuj ne scias, 1C:Enterprise estas medio por rapida disvolviĝo de transplatformaj komercaj aplikoj kaj rultempo por ilia ekzekuto en malsamaj OS-oj kaj DBMS-oj. Ĝenerale, la produkto enhavas:

  • Aplika Servila Areto, funkcias per Vindozo kaj Linukso
  • Kliento, laborante kun la servilo per http(j) aŭ ĝia propra binara protokolo, funkcias ĉe Vindozo, Linukso, macOS
  • Reta kliento, funkciante en retumiloj Chrome, Internet Explorer, Microsoft Edge, Fajrovulpo, Safari (skribitaj en JavaScript)
  • Disvolva medio (Agordilo), funkcias en Vindozo, Linukso, macOS
  • Administraj iloj aplikaĵoserviloj, funkcii per Vindozo, Linukso, macOS
  • Poŝtelefona kliento, konektanta al la servilo per http(j), funkcias ĉe porteblaj aparatoj, kiuj funkcias per Android, iOS, Vindozo
  • Poŝtelefona platformo — kadro por krei eksterretajn moveblajn aplikojn kun la kapablo sinkronigi, funkciante en Android, iOS, Vindozo
  • Disvolva medio 1C: Iloj pri Entreprena Disvolviĝo, skribita en Java
  • Servilo Interagaj Sistemoj

Ni provas skribi la saman kodon por malsamaj operaciumoj kiel eble plej multe - la servila koda bazo estas 99% ofta, la klienta koda bazo estas ĉirkaŭ 95%. La 1C:Enterprise-teknologia platformo estas ĉefe skribita en C++ kaj proksimumaj kodaj trajtoj estas donitaj malsupre:

  • 10 milionoj da linioj de C++-kodo,
  • 14 mil dosieroj,
  • 60 mil klasoj,
  • duonmiliono da metodoj.

Kaj ĉiuj ĉi aĵoj devis esti tradukitaj en C++14. Hodiaŭ ni rakontos al vi kiel ni faris tion kaj kion ni renkontis en la procezo.

Kiel ni tradukis 10 milionojn da linioj de C++-kodo al la C++14-normo (kaj poste al C++17)

Malgarantio

Ĉio ĉi-sube skribita pri malrapida/rapida laboro, (ne) granda memorkonsumo per realigoj de normaj klasoj en diversaj bibliotekoj signifas unu aferon: tio estas vera POR NI. Estas tute eble, ke normaj efektivigoj estos plej taŭgaj por viaj taskoj. Ni komencis de niaj propraj taskoj: ni prenis datumojn, kiuj estis tipaj por niaj klientoj, prizorgis tipajn scenarojn sur ili, rigardis rendimenton, la kvanton da memoro konsumita ktp., kaj analizis ĉu ni kaj niaj klientoj estas kontentaj kun tiaj rezultoj aŭ ne. . Kaj ili agis depende de.

Kion ni havis

Komence, ni skribis la kodon por la platformo 1C:Enterprise 8 uzante Microsoft Visual Studio. La projekto komenciĝis en la fruaj 2000-aj jaroj kaj ni havis nur Vindozan version. Nature, ekde tiam la kodo aktive disvolviĝis, multaj mekanismoj estis tute reverkitaj. Sed la kodo estis skribita laŭ la normo de 1998, kaj, ekzemple, niaj ortaj krampoj estis apartigitaj per spacoj por ke kompilo sukcesu, jene:

vector<vector<int> > IntV;

En 2006, kun la publikigo de platformversio 8.1, ni komencis subteni Linukson kaj ŝanĝis al triaparta norma biblioteko. STLPorto. Unu el la kialoj de la transiro estis labori kun larĝaj linioj. En nia kodo, ni uzas std::wstring, kiu baziĝas sur la wchar_t-tipo, ĉie. Ĝia grandeco en Vindozo estas 2 bajtoj, kaj en Linukso la defaŭlta estas 4 bajtoj. Ĉi tio kondukis al nekongruo de niaj binaraj protokoloj inter kliento kaj servilo, same kiel diversaj persistaj datumoj. Uzante la gcc-opciojn, vi povas specifi, ke la grandeco de wchar_t dum kompilo ankaŭ estas 2 bajtoj, sed tiam vi povas forgesi pri uzado de la norma biblioteko de la kompililo, ĉar ĝi uzas glibc, kiu siavice estas kompilita por 4-bajta wchar_t. Aliaj kialoj estis pli bona efektivigo de normaj klasoj, subteno por hash-tabeloj, kaj eĉ emulado de la semantiko de moviĝado ene de ujoj, kiujn ni aktive uzis. Kaj unu plia kialo, kiel oni diras laste sed ne malplej, estis korda agado. Ni havis nian propran klason por kordoj, ĉar... Pro la specifaĵoj de nia programaro, kordoperacioj estas uzataj tre vaste kaj por ni ĉi tio estas kritika.

Nia ŝnuro baziĝas sur ŝnuraj optimumigaj ideoj esprimitaj en la fruaj 2000-aj jaroj Andrej Alexandrescu. Poste, kiam Alexandrescu laboris ĉe Facebook, laŭ lia sugesto, linio estis uzita en la Facebook-motoro kiu funkciis laŭ similaj principoj (vidu bibliotekon malsaĝeco).

Nia linio uzis du ĉefajn optimumigajn teknologiojn:

  1. Por mallongaj valoroj, interna bufro en la ĉenobjekto mem estas uzata (ne postulante plian memor-atribuon).
  2. Por ĉiuj aliaj, mekaniko estas uzata Kopiu Sur Skribu. La kordvaloro estas konservita en unu loko, kaj referenca nombrilo estas uzata dum asigno/modifo.

Por akceli platforman kompiladon, ni ekskludis la fluan efektivigon de nia STLPort-variaĵo (kiun ni ne uzis), ĉi tio donis al ni ĉirkaŭ 20% pli rapidan kompilon. Poste ni devis fari limigitan uzon akcelon. Boost multe uzas stream, precipe en siaj servaj API-oj (ekzemple por registri), do ni devis modifi ĝin por forigi la uzon de stream. Ĉi tio siavice malfaciligis al ni migri al novaj versioj de Boost.

Tria vojo

Kiam ni moviĝis al la normo C++ 14, ni konsideris la jenajn eblojn:

  1. Ĝisdatigu la STLPort, kiun ni modifis al la C++14-normo. La opcio estas tre malfacila, ĉar... subteno por STLPort estis ĉesigita en 2010, kaj ni mem devus konstrui ĝian tutan kodon.
  2. Transiro al alia STL-efektivigo kongrua kun C++14. Estas tre dezirinde, ke ĉi tiu efektivigo estu por Vindozo kaj Linukso.
  3. Dum kompilo por ĉiu OS, uzu la bibliotekon konstruitan en la responda kompililo.

La unua opcio estis malakceptita pro tro da laboro.

Ni pensis pri la dua eblo dum kelka tempo; konsiderata kiel kandidato libc++, sed tiutempe ĝi ne funkciis sub Vindozo. Por porti libc++ al Vindozo, vi devus fari multan laboron - ekzemple skribu mem ĉion, kio rilatas al fadenoj, fadensinkronigado kaj atomeco, ĉar libc++ uzata en ĉi tiuj areoj. POSIX API.

Kaj ni elektis la trian vojon.

Transiro

Do, ni devis anstataŭigi la uzon de STLPort per la bibliotekoj de la respondaj kompililoj (Visual Studio 2015 por Vindozo, gcc 7 por Linukso, clang 8 por macOS).

Feliĉe, nia kodo estis skribita ĉefe laŭ gvidlinioj kaj ne uzis ĉiajn lertajn lertaĵojn, do la migrado al novaj bibliotekoj iris relative glate, kun la helpo de skriptoj, kiuj anstataŭigis la nomojn de tipoj, klasoj, nomspacoj kaj inkluzivas en la fonto. dosierojn. La migrado influis 10 fontdosieroj (el 000). wchar_t estis anstataŭigita per char14_t; ni decidis forlasi la uzon de wchar_t, ĉar char000_t prenas 16 bajtojn en ĉiuj OS-oj kaj ne difektas kodkongruon inter Vindozo kaj Linukso.

Estis kelkaj malgrandaj aventuroj. Ekzemple, en STLPort iteratoro povus esti implicite ĵetita al montrilo al elemento, kaj en kelkaj lokoj en nia kodo tio estis uzata. En novaj bibliotekoj ne plu eblis fari tion, kaj ĉi tiuj fragmentoj devis esti analizitaj kaj reverkitaj permane.

Do, la koda migrado estas kompleta, la kodo estas kompilita por ĉiuj operaciumoj. Estas tempo por provoj.

Testoj post la transiro montris malpliiĝon de rendimento (kelkaj lokoj ĝis 20-30%) kaj pliiĝon de memorkonsumo (ĝis 10-15%) kompare kun la malnova versio de la kodo. Tio estis, aparte, pro la suboptimuma prezento de normaj kordoj. Tial, ni denove devis uzi nian propran, iomete modifitan linion.

Interesa trajto de la efektivigo de ujoj en enigitaj bibliotekoj ankaŭ estis malkaŝita: malplena (sen elementoj) std::map kaj std::set el enkonstruitaj bibliotekoj asigni memoron. Kaj pro la efektivigaĵoj, en iuj lokoj en la kodo estas kreitaj sufiĉe multaj malplenaj ujoj de ĉi tiu tipo. Normaj memorujoj estas iomete asignitaj, por unu radika elemento, sed por ni tio montriĝis maltrankviliga - en kelkaj scenaroj, nia rendimento signife malpliiĝis kaj memorkonsumo pliiĝis (kompare kun STLPort). Tial en nia kodo ni anstataŭigis ĉi tiujn du specojn de ujoj de la enkonstruitaj bibliotekoj per ilia efektivigo de Boost, kie ĉi tiuj ujoj ne havis ĉi tiun funkcion, kaj ĉi tio solvis la problemon kun malrapidiĝo kaj pliigita memorkonsumo.

Kiel ofte okazas post grandskalaj ŝanĝoj en grandaj projektoj, la unua ripeto de la fontkodo ne funkciis senprobleme, kaj ĉi tie, precipe, subteno por sencimigaj iteratoroj en la Vindoza efektivigo estis oportuna. Paŝon post paŝo ni antaŭeniris, kaj ĝis la printempo de 2017 (versio 8.3.11 1C:Enterprise) la migrado estis finita.

Rezultoj

La transiro al la normo C++14 daŭris nin ĉirkaŭ 6 monatoj. Plejofte, unu (sed tre kvalifikita) programisto laboris pri la projekto, kaj ĉe la fina etapo reprezentantoj de teamoj respondecaj pri specifaj areoj aliĝis al - UI, servila areto, evoluaj kaj administraj iloj ktp.

La transiro multe simpligis nian laboron pri migrado al la plej novaj versioj de la normo. Tiel, versio 1C:Enterprise 8.3.14 (en evoluo, eldono planita por frua venonta jaro) jam estis translokigita al la normo C++ 17.

Post la migrado, programistoj havas pli da ebloj. Se pli frue ni havis nian propran modifitan version de STL kaj unu std-nomspacon, nun ni havas normajn klasojn de la enkonstruitaj kompil-bibliotekoj en la std-nomspaco, en la stdx-nomspaco - niaj linioj kaj ujoj optimumigitaj por niaj taskoj, en boost - la lasta versio de boost. Kaj la programisto uzas tiujn klasojn, kiuj optimume taŭgas por solvi siajn problemojn.

La "denaska" efektivigo de movaj konstrukciistoj ankaŭ helpas en evoluo (movi konstrukciistojn) por kelkaj klasoj. Se klaso havas movan konstruilon kaj ĉi tiu klaso estas metita en ujo, tiam la STL optimumigas la kopiadon de elementoj ene de la ujo (ekzemple, kiam la ujo estas vastigita kaj necesas ŝanĝi kapaciton kaj reasigni memoron).

Kulero da gudro

Eble la plej malagrabla (sed ne kritika) sekvo de migrado estas, ke ni alfrontas pliiĝon de la volumo. obj dosieroj, kaj la plena rezulto de la konstruo kun ĉiuj mezaj dosieroj komencis okupi 60–70 GB. Ĉi tiu konduto ŝuldiĝas al la proprecoj de modernaj normaj bibliotekoj, kiuj fariĝis malpli kritikaj de la grandeco de generitaj servodosieroj. Ĉi tio ne influas la funkciadon de la kompilita aplikaĵo, sed ĝi kaŭzas kelkajn malkomfortojn en la disvolviĝo, precipe ĝi pliigas la kompiltempon. La postuloj por libera diskospaco sur konstruserviloj kaj sur programmaŝinoj ankaŭ pliiĝas. Niaj programistoj laboras sur pluraj versioj de la platformo paralele, kaj centoj da gigabajtoj da mezaj dosieroj foje kreas malfacilaĵojn en sia laboro. La problemo estas malagrabla, sed ne kritika; ni prokrastis ĝian solvon por nun. Ni konsideras teknologion kiel unu el la ebloj por solvi ĝin unueco konstrui (precipe, Guglo uzas ĝin dum evoluigado de la Chrome-retumilo).

fonto: www.habr.com

Aldoni komenton