Kuinka käänsimme 10 miljoonaa riviä C++-koodia C++14-standardiin (ja sitten C++17:ään)

Jokin aika sitten (syksyllä 2016) 1C:Enterprise-teknologiaalustan seuraavan version kehittämisen aikana kehitystiimin sisällä heräsi kysymys uuden standardin tukemisesta. C ++ 14 koodissamme. Siirtyminen uuteen standardiin, kuten oletimme, antaisi meille mahdollisuuden kirjoittaa monia asioita tyylikkäämmin, yksinkertaisemmin ja luotettavammin, ja se yksinkertaistaisi koodin tukea ja ylläpitoa. Ja käännöksessä ei näytä olevan mitään erikoista, ellei koodipohjan laajuus ja koodimme erityispiirteet.

Niille, jotka eivät tiedä, 1C:Enterprise on ympäristö useiden alustojen välisten yrityssovellusten nopeaan kehittämiseen ja niiden suorittamiseen eri käyttöjärjestelmissä ja DBMS-järjestelmissä. Yleisesti ottaen tuote sisältää:

  • Sovelluspalvelinklusteri, toimii Windowsissa ja Linuxissa
  • asiakas, toimii palvelimen kanssa http(t) tai oman binaariprotokollansa kautta, toimii Windowsissa, Linuxissa, macOS:ssä
  • Web-asiakas, joka toimii Chrome-, Internet Explorer-, Microsoft Edge-, Firefox- ja Safari-selaimissa (kirjoitettu JavaScriptillä)
  • Kehitysympäristö (Konfiguraattori), toimii Windowsissa, Linuxissa ja macOS:ssä
  • Hallintotyökalut sovelluspalvelimet, toimivat Windowsissa, Linuxissa ja macOS:ssä
  • Mobiili asiakas, muodostaa yhteyden palvelimeen http:n kautta, toimii mobiililaitteissa, joissa on Android, iOS tai Windows
  • Mobiili alusta — puitteet synkronoitavissa olevien offline-mobiilisovellusten luomiseen, jotka toimivat Androidissa, iOS:ssä ja Windowsissa
  • Kehitysympäristö 1C: Yrityksen kehitystyökalut, kirjoitettu Java-kielellä
  • Server Vuorovaikutusjärjestelmät

Pyrimme kirjoittamaan samaa koodia eri käyttöjärjestelmille mahdollisimman paljon - palvelinkoodikanta on 99% yhteinen, asiakaskoodikanta noin 95%. 1C:Enterprise-teknologiaalusta on ensisijaisesti kirjoitettu C++-kielellä ja likimääräiset koodiominaisuudet on annettu alla:

  • 10 miljoonaa riviä C++-koodia,
  • 14 tuhatta tiedostoa,
  • 60 tuhatta luokkaa,
  • puoli miljoonaa menetelmää.

Ja kaikki tämä tavara piti kääntää C++14:ksi. Tänään kerromme sinulle, kuinka teimme tämän ja mitä kohtasimme prosessissa.

Kuinka käänsimme 10 miljoonaa riviä C++-koodia C++14-standardiin (ja sitten C++17:ään)

Vastuuvapauslauseke

Kaikki alla kirjoitettu hitaasta/nopeasta työstä, (ei) suuresta muistinkulutuksesta standardiluokkien toteutuksissa eri kirjastoissa tarkoittaa yhtä asiaa: tämä on totta MEILLE. On täysin mahdollista, että standarditoteutukset sopivat parhaiten tehtäviisi. Lähdimme omista tehtävistämme: otimme asiakkaillemme tyypillisiä tietoja, suoritimme heille tyypillisiä skenaarioita, tarkastelimme suorituskykyä, kulutetun muistin määrää jne. ja analysoimme, olemmeko me ja asiakkaamme tyytyväisiä näihin tuloksiin vai emme. . Ja he toimivat riippuen.

Mitä meillä oli

Aluksi kirjoitimme koodin 1C:Enterprise 8 -alustalle Microsoft Visual Studiossa. Projekti alkoi 2000-luvun alussa ja meillä oli vain Windows-versio. Luonnollisesti sen jälkeen koodia on kehitetty aktiivisesti, monet mekanismit on kirjoitettu kokonaan uudelleen. Mutta koodi on kirjoitettu vuoden 1998 standardin mukaan, ja esimerkiksi oikeat kulmasulut erotettiin välilyönnillä, jotta käännös onnistuisi, näin:

vector<vector<int> > IntV;

Vuonna 2006 alustan version 8.1 julkaisun myötä aloimme tukea Linuxia ja vaihdoimme kolmannen osapuolen standardikirjastoon STLPort. Yksi siirtymän syistä oli työskennellä leveillä linjoilla. Koodissamme käytämme kaikkialla std::wstringiä, joka perustuu wchar_t-tyyppiin. Sen koko on Windowsissa 2 tavua ja Linuxissa oletusarvo on 4 tavua. Tämä johti binääriprotokolliemme yhteensopimattomuuteen asiakkaan ja palvelimen välillä sekä useisiin pysyviin tietoihin. Käyttämällä gcc-asetuksia voit määrittää, että wchar_t:n koko on myös käännöksen aikana 2 tavua, mutta silloin voit unohtaa kääntäjän vakiokirjaston käyttämisen, koska se käyttää glibc:tä, joka puolestaan ​​on käännetty 4-tavuiselle wchar_t-tiedostolle. Muita syitä olivat standardiluokkien parempi toteutus, hash-taulukoiden tuki ja jopa konttien sisällä liikkumisen semantiikan emulointi, jota käytimme aktiivisesti. Ja vielä yksi syy, kuten sanotaan viimeisenä muttei vähäisimpänä, oli jousiesitys. Meillä oli oma luokka jousille, koska... Ohjelmistomme erityispiirteistä johtuen merkkijonooperaatioita käytetään erittäin laajasti ja tämä on meille kriittistä.

Merkkijonomme perustuu jo 2000-luvun alussa ilmaistuihin merkkijonojen optimointiideoihin Andrei Alexandrescu. Myöhemmin, kun Alexandrescu työskenteli Facebookissa, hänen ehdotuksestaan ​​Facebook-moottorissa käytettiin linjaa, joka toimi samanlaisilla periaatteilla (katso kirjasto hulluutta).

Linjamme käytti kahta pääasiallista optimointitekniikkaa:

  1. Lyhyille arvoille käytetään sisäistä puskuria itse merkkijonoobjektissa (ei vaadi lisämuistin varaamista).
  2. Kaikille muille käytetään mekaniikkaa Kopioi kirjoitettaessa. Merkkijonoarvo tallennetaan yhteen paikkaan, ja osoituksen/muokkauksen aikana käytetään referenssilaskuria.

Alustan kääntämisen nopeuttamiseksi poistimme stream-toteutuksen STLPort-versiostamme (jota emme käyttäneet), tämä antoi meille noin 20% nopeamman käännöksen. Myöhemmin meidän piti käyttää rajoitetusti edistää. Boost käyttää paljon streamia, erityisesti sen palvelusovellusliittymissä (esimerkiksi kirjaamiseen), joten jouduimme muokkaamaan sitä streamin käytön poistamiseksi. Tämä puolestaan ​​vaikeutti siirtymistä uusiin Boost-versioihin.

Kolmas tapa

Siirtyessämme C++14-standardiin harkitsimme seuraavia vaihtoehtoja:

  1. Päivitä muokkaamamme STLPort C++14-standardiin. Vaihtoehto on erittäin vaikea, koska... STLPortin tuki lopetettiin vuonna 2010, ja meidän pitäisi rakentaa kaikki sen koodi itse.
  2. Siirtyminen toiseen C++14:n kanssa yhteensopivaan STL-toteutukseen. On erittäin toivottavaa, että tämä toteutus on Windowsille ja Linuxille.
  3. Kun käännät jokaiselle käyttöjärjestelmälle, käytä vastaavaan kääntäjään sisäänrakennettua kirjastoa.

Ensimmäinen vaihtoehto hylättiin suoraan liian suuren työn vuoksi.

Mietimme toista vaihtoehtoa jonkin aikaa; pidetään ehdokkaana libc++, mutta tuolloin se ei toiminut Windowsissa. Libc++:n siirtäminen Windowsiin joutuisi tekemään paljon työtä - esimerkiksi kirjoittamaan itse kaikki, mikä liittyy säikeiden, säikeiden synkronointiin ja atomiteettiin, koska libc++:aa käytetään näillä alueilla. POSIX API.

Ja valitsimme kolmannen tavan.

siirtyminen

Joten meidän piti korvata STLPortin käyttö vastaavien kääntäjien kirjastoilla (Visual Studio 2015 Windowsille, gcc 7 Linuxille, clang 8 macOS:lle).

Onneksi koodimme oli kirjoitettu pääosin ohjeiden mukaan eikä siinä käytetty kaikenlaisia ​​fiksuja temppuja, joten siirtyminen uusiin kirjastoihin sujui suhteellisen sujuvasti skriptien avulla, jotka korvasivat tyyppien, luokkien, nimiavaruuksien ja sisällytyksiä lähteessä. tiedostot. Siirto vaikutti 10 000 lähdetiedostoon (14 000:sta). wchar_t korvattiin char16_t:lla; päätimme luopua wchar_t:n käytöstä, koska char16_t vie 2 tavua kaikissa käyttöjärjestelmissä eikä pilaa koodin yhteensopivuutta Windowsin ja Linuxin välillä.

Pieniä seikkailuja oli. Esimerkiksi STLPortissa iteraattori voitiin implisiittisesti heittää osoittimeen elementtiin, ja joissain paikoissa koodissamme tätä käytettiin. Uusissa kirjastoissa tämä ei enää ollut mahdollista, vaan nämä kohdat piti analysoida ja kirjoittaa uudelleen manuaalisesti.

Joten koodin siirto on valmis, koodi on käännetty kaikille käyttöjärjestelmille. On testien aika.

Siirron jälkeiset testit osoittivat suorituskyvyn laskua (joissain paikoissa jopa 20-30 %) ja muistin kulutuksen lisääntymistä (jopa 10-15 %) verrattuna koodin vanhaan versioon. Tämä johtui erityisesti standardikielien epäoptimaalisesta suorituskyvystä. Siksi jouduimme jälleen käyttämään omaa, hieman muokattua linjaamme.

Myös mielenkiintoinen piirre säilöjen toteutuksessa sulautetuissa kirjastoissa paljastui: tyhjät (ilman elementtejä) std::map ja std::set sisäänrakennetuista kirjastoista varaavat muistia. Ja toteutusominaisuuksien vuoksi paikoin koodissa syntyy melko paljon tämän tyyppisiä tyhjiä säiliöitä. Vakiomuistisäiliöitä on varattu vähän, yhdelle juurielementille, mutta meille tämä osoittautui kriittiseksi - useissa skenaarioissa suorituskykymme laski merkittävästi ja muistin kulutus kasvoi (verrattuna STLPortiin). Siksi koodissamme korvasimme nämä kaksi konttityyppiä sisäänrakennetuista kirjastoista niiden toteutuksella Boostista, jossa näissä säilöissä ei ollut tätä ominaisuutta, ja tämä ratkaisi ongelman hidastumisella ja lisääntyneellä muistinkulutuksella.

Kuten usein tapahtuu suurten projektien laajamittaisten muutosten jälkeen, lähdekoodin ensimmäinen iteraatio ei toiminut ongelmitta, ja erityisesti tässä Windows-toteutuksen vianetsintäiteraattorien tuki oli hyödyllinen. Askel askeleelta etenimme, ja kevääseen 2017 mennessä (versio 8.3.11 1C:Enterprise) migraatio saatiin päätökseen.

Tulokset

Siirtyminen C++14-standardiin kesti noin 6 kuukautta. Suurimman osan ajasta yksi (mutta erittäin pätevä) kehittäjä työskenteli projektin parissa ja loppuvaiheessa liittyi tietyistä alueista vastaavien tiimien edustajat - käyttöliittymä, palvelinklusteri, kehitys- ja hallintatyökalut jne.

Siirtyminen yksinkertaisti huomattavasti työtämme siirtyessämme standardin uusimpiin versioihin. Näin ollen versio 1C:Enterprise 8.3.14 (kehitellään, julkaistaan ​​ensi vuoden alussa) on jo siirretty standardiin C++17.

Siirron jälkeen kehittäjillä on enemmän vaihtoehtoja. Jos aiemmin meillä oli oma muokattu versio STL:stä ja yksi std-nimiavaruus, nyt meillä on vakioluokat sisäänrakennetuista kääntäjäkirjastoista std-nimiavaruudessa, stdx-nimiavaruudessa - rivimme ja säilömme on optimoitu tehtäviämme varten, tehosteessa - boostin uusin versio. Ja kehittäjä käyttää niitä luokkia, jotka sopivat optimaalisesti hänen ongelmiensa ratkaisemiseen.

Move-konstruktorien "natiivi" toteutus auttaa myös kehityksessä (siirtää rakentajia) useille luokille. Jos luokassa on siirtokonstruktori ja tämä luokka sijoitetaan säilöön, niin STL optimoi elementtien kopioinnin säilön sisällä (esimerkiksi kun konttia laajennetaan ja on tarpeen muuttaa kapasiteettia ja kohdistaa uudelleen muistia).

Liota voiteessa

Ehkä epämiellyttävin (mutta ei kriittisin) muuttoliikkeen seuraus on se, että kohtaamme määrän kasvun obj-tiedostoja, ja koko koontitulos kaikkine välitiedostoineen alkoi viedä 60–70 Gt. Tämä käyttäytyminen johtuu nykyaikaisten standardikirjastojen erityispiirteistä, jotka ovat tulleet vähemmän kriittisiksi luotujen palvelutiedostojen koon suhteen. Tämä ei vaikuta käännetyn sovelluksen toimintaan, mutta aiheuttaa useita haittoja kehityksessä, erityisesti lisää käännösaikaa. Myös rakennuspalvelimien ja kehittäjälaitteiden vapaan levytilan vaatimukset kasvavat. Kehittäjämme työskentelevät useiden alustan versioiden parissa rinnakkain, ja sadat gigatavut välitiedostot aiheuttavat joskus vaikeuksia heidän työssään. Ongelma on epämiellyttävä, mutta ei kriittinen; olemme toistaiseksi lykänneet sen ratkaisua. Harkitsemme teknologiaa yhtenä ratkaisuna sen ratkaisemiseksi yhtenäisyyden rakentaminen (etenkin Google käyttää sitä kehittäessään Chrome-selainta).

Lähde: will.com

Lisää kommentti