C++ Venäjä: miten se tapahtui

Jos näytelmän alussa sanot, että seinällä roikkuu C++-koodi, niin lopussa se väistämättä ampuu sinua jalkaan.

Bjarne Stroustrup

31. lokakuuta - 1. marraskuuta pidettiin Pietarissa C++ Russia Piter -konferenssi, yksi Venäjän suurista ohjelmointikonferensseista, jonka järjesti JUG Ru Group. Kutsuttuja puhujia ovat C++-standardikomitean jäsenet, CppCon-puhujat, O'Reilly-kirjojen kirjoittajat ja projektien ylläpitäjät, kuten LLVM, libc++ ja Boost. Konferenssi on suunnattu kokeneille C++-kehittäjille, jotka haluavat syventää osaamistaan ​​ja vaihtaa kokemuksia live-viestinnästä. Opiskelijoille, jatko-opiskelijoille ja yliopiston opettajille tarjotaan erittäin mukavia alennuksia.

Konferenssin Moskovan versio on nähtävillä jo ensi vuoden huhtikuussa, mutta sillä välin opiskelijamme kertovat, mitä mielenkiintoista he oppivat viime tapahtumassa. 

C++ Venäjä: miten se tapahtui

Kuva konferenssin albumi

meistä

Kaksi opiskelijaa National Research University Higher School of Economics - Pietarista työskenteli tässä postauksessa:

  • Liza Vasilenko on 4. vuoden perustutkinto-opiskelija, joka opiskelee ohjelmointikieliä osana Applied Mathematics and Computer Science -ohjelmaa. Tutustuttuani C++-kieleen ensimmäisenä yliopistovuotena, sain myöhemmin kokemusta sen kanssa työskentelystä alan harjoittelujaksojen kautta. Intohimoni ohjelmointikieliin yleensä ja toiminnalliseen ohjelmointiin erityisesti jätti jälkensä konferenssin raporttien valintaan.
  • Danya Smirnov on 1. vuoden opiskelija "Ohjelmointi ja data-analyysi" maisteriohjelmassa. Vielä koulussa kirjoitin olympiatehtäviä C++-kielellä, ja sitten jotenkin kävi niin, että kieli nousi jatkuvasti opetustoiminnassa ja tuli lopulta päätyökieleksi. Päätin osallistua konferenssiin parantaakseni tietojani ja oppiakseni myös uusia mahdollisuuksia.

Uutiskirjeessä tiedekunnan johto jakaa usein tietoa erikoisalaamme liittyvistä koulutustapahtumista. Syyskuussa näimme tietoa C++ Russiasta ja päätimme rekisteröityä kuuntelijoiksi. Tämä on ensimmäinen kokemuksemme osallistua tällaisiin konferensseihin.

Konferenssin rakenne

  • Raportit

Asiantuntijat lukivat kahden päivän aikana 30 raporttia, jotka käsittelivät monia kuumia aiheita: kieliominaisuuksien nerokkaita käyttöjä sovellettujen ongelmien ratkaisemiseen, uuteen standardiin liittyviä tulevia kielipäivityksiä, kompromisseja C++-suunnittelussa ja varotoimia niiden seurausten käsittelyssä, esimerkkejä mielenkiintoisesta projektiarkkitehtuurista sekä joitain yksityiskohtia kieliinfrastruktuurista. Kolme esitystä pidettiin samanaikaisesti, useimmiten kaksi venäjäksi ja yksi englanniksi.

  • Keskustelualueet

Puheen jälkeen kaikki esittämättömät kysymykset ja keskeneräiset keskustelut siirrettiin erityisesti puhujien kanssa kommunikointiin varatuille alueille, jotka oli varustettu merkkitauluilla. Hyvä tapa pitää tauko puheiden välillä miellyttävän keskustelun parissa.

  • Salamakeskustelut ja epäviralliset keskustelut

Jos haluat antaa lyhyen raportin, voit ilmoittautua taululle illan Lightning Talkiin ja saada viisi minuuttia aikaa puhua mistä tahansa konferenssin aiheesta. Esimerkiksi nopea johdatus C++:n desinfiointiaineisiin (joillekin se oli uutta) tai tarina siniaallon sukupolven virheestä, joka voidaan vain kuulla, mutta ei nähdä.

Toinen muoto on paneelikeskustelu "Sydämestä sydämeen -komitea". Lavalla on standardointikomitean jäseniä, projektorissa takka (virallisesti - luomaan vilpitöntä tunnelmaa, mutta syy "koska KAIKKI ON TULEE" vaikuttaa hauskempaa), kysymyksiä standardista ja C++:n yleisnäkemyksestä. , ilman kiihkeitä teknisiä keskusteluja ja holiwars. Kävi ilmi, että komiteassa on myös eläviä ihmisiä, jotka eivät ehkä ole jostain täysin varmoja tai eivät tiedä jotakin.

Holivarien ystäville kolmas tapahtuma jäi koteloon - BOF-istunto "Go vs. C++". Otamme Go-ihmisen, C++:n rakastajan, ennen istunnon alkua he yhdessä valmistelevat 100500 XNUMX diaa jostakin aiheesta (kuten C++:n pakettien ongelmat tai Go:n geneeristen tuotteiden puute), ja sitten he keskustelevat keskenään ja yleisön kanssa, ja yleisö yrittää ymmärtää kahta näkökulmaa kerralla . Jos holivar alkaa kontekstista, moderaattori puuttuu asiaan ja sovittaa osapuolet. Tämä muoto on koukuttava: useita tunteja alun jälkeen vain puolet dioista valmistui. Loppua piti kiihdyttää suuresti.

  • Kumppani seisoo

Konferenssin yhteistyökumppanit olivat edustettuina halleissa - osastoilla keskusteltiin ajankohtaisista projekteista, tarjottiin harjoittelu- ja työpaikkoja, järjestettiin tietokilpailuja ja pieniä kilpailuja sekä arvottiin kivoja palkintoja. Samaan aikaan jotkut yritykset jopa tarjoutuivat käymään läpi haastattelujen alkuvaiheet, mikä voisi olla hyödyllistä niille, jotka tulivat paitsi kuuntelemaan raportteja.

Raporttien tekniset tiedot

Kuuntelimme raportteja molempina päivinä. Välillä oli vaikeaa valita yhtä raporttia rinnakkaisista - sovittiin, että eroamme ja vaihdamme taukojen aikana hankittua tietoa. Ja silti näyttää siltä, ​​että paljon on jätetty pois. Tässä haluaisimme puhua joidenkin kiinnostavimpien raporttien sisällöstä

Poikkeuksia C++:ssa kääntäjien optimoinnin prisman kautta, Roman Rusyaev

C++ Venäjä: miten se tapahtui
Dia alkaen esitykset

Kuten otsikko viittaa, Roman tarkasteli työskentelyä poikkeuksin käyttämällä esimerkkinä LLVM:ää. Samaan aikaan niille, jotka eivät käytä Clangia työssään, raportti voi silti antaa jonkinlaisen käsityksen siitä, kuinka koodia voitaisiin mahdollisesti optimoida. Tämä johtuu siitä, että kääntäjien ja vastaavien standardikirjastojen kehittäjät kommunikoivat keskenään ja monet onnistuneet ratkaisut voivat osua yhteen.

Joten poikkeuksen käsittelemiseksi sinun on tehtävä monia asioita: kutsuttava käsittelykoodi (jos sellainen on) tai vapautettava resurssit nykyisellä tasolla ja nostettava pinoa korkeammalle. Kaikki tämä johtaa siihen, että kääntäjä lisää lisäohjeita kutsuille, jotka mahdollisesti aiheuttavat poikkeuksia. Siksi, jos poikkeusta ei todellakaan noudateta, ohjelma suorittaa silti tarpeettomia toimia. LLVM:ssä on useita heuristiikkaa tilanteiden määrittämiseksi, joissa poikkeuskäsittelykoodia ei tarvitse lisätä tai "ylimääräisten" käskyjen määrää voidaan vähentää jollain tavalla vähentääkseen lisäkustannuksia.

Puhuja tarkastelee niitä noin tusinaa ja näyttää sekä tilanteita, joissa ne nopeuttavat ohjelman suorittamista, että niitä, joissa nämä menetelmät eivät sovellu.

Siten Roman Rusyaev johtaa opiskelijat siihen johtopäätökseen, että poikkeuskäsittelyä sisältävää koodia ei aina voida suorittaa ilman lisäkustannuksia, ja antaa seuraavan neuvon:

  • kirjastoja kehitettäessä kannattaa periaatteessa luopua poikkeuksista;
  • jos poikkeuksia vielä tarvitaan, niin aina kun mahdollista, kannattaa lisätä noexcept (ja const) -määritteitä kaikkialle, jotta kääntäjä pystyy optimoimaan mahdollisimman paljon.

Yleisesti ottaen puhuja vahvisti näkemyksen, että poikkeukset on parasta käyttää minimiin tai hylätä ne kokonaan.

Raporttidiat ovat saatavilla seuraavasta linkistä: ["C++-poikkeukset LLVM-kääntäjän optimoinnin linssin kautta"]

Generaattorit, korutiinit ja muut aivot purkavat herkut, Adi Shavit

C++ Venäjä: miten se tapahtui
Dia alkaen esitykset

Yksi tämän C++20:n innovaatioille omistetun konferenssin monista raporteista oli mieleenpainuva paitsi värikkäästä esityksestään myös siitä, että se tunnistaa selkeästi kokoelman käsittelylogiikassa (silmukalle, takaisinkutsuille) liittyvät ongelmat.

Adi Shavit korostaa seuraavaa: tällä hetkellä saatavilla olevat menetelmät käyvät läpi koko kokoelman eivätkä anna pääsyä johonkin sisäiseen välitilaan (tai ne tekevät takaisinsoittojen tapauksessa, mutta joilla on suuri määrä epämiellyttäviä sivuvaikutuksia, kuten Callback Hell) . Vaikuttaa siltä, ​​​​että iteraattoreita on, mutta niidenkään kanssa kaikki ei ole niin sujuvaa: ei ole yhteisiä tulo- ja poistumispisteitä (alku → loppu vs. rbegin → rend ja niin edelleen), ei ole selvää, kuinka kauan iteroidaan? C++20:stä alkaen nämä ongelmat on ratkaistu!

Ensimmäinen vaihtoehto: alueet. Käärimällä iteraattorit saamme yhteisen käyttöliittymän iteroinnin alkuun ja loppuun, ja saamme myös kyvyn säveltää. Kaikki tämä helpottaa täysimittaisten tietojenkäsittelyputkien rakentamista. Mutta kaikki ei ole niin sujuvaa: osa laskentalogiikasta sijaitsee tietyn iteraattorin toteutuksessa, mikä voi monimutkaistaa koodin ymmärtämistä ja virheenkorjausta.

C++ Venäjä: miten se tapahtui
Dia alkaen esitykset

No, tässä tapauksessa C++20 lisäsi korutiinit (funktiot, joiden käyttäytyminen on samanlaista kuin Pythonin generaattorit): suoritusta voidaan lykätä palauttamalla jokin nykyinen arvo säilyttäen samalla välitilan. Näin ollen saavutamme paitsi työskentelyn datan kanssa sellaisena kuin se näyttää, myös kapseloimme kaiken logiikan tietyn korutiinin sisään.

Mutta siinä on kärpänen: tällä hetkellä olemassa olevat kääntäjät tukevat niitä vain osittain, eikä niitä myöskään ole toteutettu niin siististi kuin haluaisimme: esimerkiksi viitteitä ja väliaikaisia ​​objekteja ei kannata vielä käyttää korutiineissa. Lisäksi on joitain rajoituksia sille, mitkä voivat olla korutiineja, ja constexpr-funktiot, konstruktorit/destruktorit ja main eivät sisälly tähän luetteloon.

Näin ollen korutiinit ratkaisevat merkittävän osan ongelmista tiedonkäsittelylogiikan yksinkertaisuudella, mutta niiden nykyiset toteutukset vaativat parannusta.

materiaalit:

C++ temppuja Yandex.Taxilta, Anton Polukhin

Ammattitoiminnassani joudun joskus toteuttamaan puhtaasti apuasioita: käärettä jonkin kirjaston sisäisen rajapinnan ja API:n välillä, kirjaamista tai jäsentämistä. Tässä tapauksessa ei yleensä tarvita lisäoptimointia. Mutta entä jos näitä komponentteja käytetään joissakin RuNetin suosituimmista palveluista? Tällaisessa tilanteessa joudut käsittelemään teratavuja tunnissa pelkästään lokeja! Silloin jokainen millisekunti on tärkeä, ja siksi sinun on turvauduttava erilaisiin temppuihin - Anton Polukhin puhui niistä.

Ehkä mielenkiintoisin esimerkki oli pointer-to-implementation (pimpl) -mallin toteutus. 

#include <third_party/json.hpp> //PROBLEMS! 
struct Value { 
    Value() = default; 
    Value(Value&& other) = default; 
    Value& operator=(Value&& other) = default; 
    ~Value() = default; 

    std::size_t Size() const { return data_.size(); } 

private: 
    third_party::Json data_; 
};

Tässä esimerkissä haluan ensin päästä eroon ulkoisten kirjastojen otsikkotiedostoista - tämä käännetään nopeammin ja voit suojautua mahdollisilta nimiristiriidoilta ja muilta vastaavilta virheiltä. 

Okei, siirsimme #include .cpp-tiedostoon: tarvitsemme tiivistetyn API:n eteenpäinilmoituksen sekä std::unique_ptr. Nyt meillä on dynaamisia allokaatioita ja muita epämiellyttäviä asioita, kuten dataa hajallaan datajoukon yli ja alennetut takuut. std::aligned_storage voi auttaa kaikessa tässä. 

struct Value { 
// ... 
private: 
    using JsonNative = third_party::Json; 
    const JsonNative* Ptr() const noexcept; 
    JsonNative* Ptr() noexcept; 

    constexpr std::size_t kImplSize = 32; 
    constexpr std::size_t kImplAlign = 8; 
    std::aligned_storage_t<kImplSize, kImplAlign> data_; 
};

Ainoa ongelma: sinun on määritettävä koko ja kohdistus jokaiselle kääreelle - tehdään pimpl-mallimme parametreilla , käytä mielivaltaisia ​​arvoja ja lisää tuhoajaan tarkistus, että meillä on kaikki oikein: 

~FastPimpl() noexcept { 
    validate<sizeof(T), alignof(T)>(); 
    Ptr()->~T(); 
}

template <std::size_t ActualSize, std::size_t ActualAlignment>
static void validate() noexcept { 
    static_assert(
        Size == ActualSize, 
        "Size and sizeof(T) mismatch"
    ); 
    static_assert(
        Alignment == ActualAlignment, 
        "Alignment and alignof(T) mismatch"
    ); 
}

Koska T on jo määritetty destruktoria käsiteltäessä, tämä koodi jäsennetään oikein ja käännösvaiheessa se tulostaa vaaditun koon ja kohdistusarvot, jotka on syötettävä virheinä. Näin ollen yhden ylimääräisen käännösajon kustannuksella pääsemme eroon käärittyjen luokkien dynaamisesta allokoinnista, piilotamme API:n toteutuksen mukana .cpp-tiedostoon ja saamme myös mallin, joka sopii paremmin prosessorin välimuistiin.

Kirjaaminen ja jäsentäminen vaikuttivat vähemmän vaikuttavalta, joten niitä ei mainita tässä katsauksessa.

Raporttidiat ovat saatavilla seuraavasta linkistä: ["C++ temppuja taksilta"]

Nykyaikaisia ​​tekniikoita koodisi KUIVAAN pitämiseen, Björn Fahller

Tässä puheessa Björn Fahller näyttää useita erilaisia ​​tapoja torjua toistuvien kuntotarkastusten tyylivirheitä:

assert(a == IDLE || a == CONNECTED || a == DISCONNECTED);

Kuulostaa tutulta? Käyttämällä useita tehokkaita C++-tekniikoita, jotka on otettu käyttöön viimeaikaisissa standardeissa, voit toteuttaa samat toiminnot tyylikkäästi ilman suorituskykyä. Vertailla:   

assert(a == any_of(IDLE, CONNECTED, DISCONNECTED));

Jos haluat käsitellä määräämättömän määrän tarkistuksia, sinun on heti käytettävä vaihtelevia malleja ja taittolausekkeita. Oletetaan, että haluamme tarkistaa useiden muuttujien yhtäläisyyden enumin state_type-elementin kanssa. Ensimmäinen asia, joka tulee mieleen, on kirjoittaa apufunktio is_any_of:


enum state_type { IDLE, CONNECTED, DISCONNECTED };

template <typename ... Ts>
bool is_any_of(state_type s, const Ts& ... ts) { 
    return ((s == ts) || ...); 
}

Tämä välitulos on pettymys. Toistaiseksi koodi ei ole tulossa luettavammaksi:

assert(is_any_of(state, IDLE, DISCONNECTING, DISCONNECTED)); 

Ei-tyyppiset malliparametrit auttavat parantamaan tilannetta hieman. Heidän avullaan siirrämme enumin luetellut elementit malliparametrien luetteloon: 

template <state_type ... states>
bool is_any_of(state_type t) { 
    return ((t == states) | ...); 
}
	
assert(is_any_of<IDLE, DISCONNECTING, DISCONNECTED>(state)); 

Käyttämällä autoa ei-tyyppisessä mallin parametrissa (C++17), lähestymistapa yksinkertaisesti yleistyy vertailuihin paitsi state_type -elementtien kanssa, myös primitiivisten tyyppien kanssa, joita voidaan käyttää ei-tyyppisinä malliparametreina:


template <auto ... alternatives, typename T>
bool is_any_of(const T& t) {
    return ((t == alternatives) | ...);
}

Näillä peräkkäisillä parannuksilla saavutetaan haluttu sujuva tarkistussyntaksi:


template <class ... Ts>
struct any_of : private std::tuple<Ts ...> { 
// поленимся и унаследуем конструкторы от tuple 
        using std::tuple<Ts ...>::tuple;
        template <typename T>
        bool operator ==(const T& t) const {
                return std::apply(
                        [&t](const auto& ... ts) {
                                return ((ts == t) || ...);
                        },
                        static_cast<const std::tuple<Ts ...>&>(*this));
        }
};

template <class ... Ts>
any_of(Ts ...) -> any_of<Ts ... >;
 
assert(any_of(IDLE, DISCONNECTING, DISCONNECTED) == state);

Tässä esimerkissä päättelyopas ehdottaa haluttuja rakennemalliparametreja kääntäjälle, joka tietää konstruktoriargumenttien tyypit. 

Lisäksi - mielenkiintoisempaa. Bjorn opettaa, kuinka tuloksena oleva koodi yleistetään vertailuoperaattoreille ==:n jälkeen ja sitten mielivaltaisille operaatioille. Matkan varrella selostetaan käyttöesimerkein sellaisia ​​ominaisuuksia kuin no_unique_address attribuutti (C++20) ja malliparametrit lambda-funktioissa (C++20). (Kyllä, nyt lambda-syntaksi on vielä helpompi muistaa - nämä ovat neljä peräkkäistä paria kaikenlaisia ​​sulkuja.) Lopullinen ratkaisu, jossa funktioita käytetään konstruktoriyksityiskohtina, lämmittää todella sielua, puhumattakaan ilmaisutuplesta lambdan parhaissa perinteissä laskenta.

Lopuksi älä unohda kiillottaa sitä:

  • Muista, että lambdat ovat constexpr ilmaiseksi; 
  • Lisätäänpä täydellinen edelleenlähetys ja katsotaan sen rumaa syntaksia suhteessa parametripakettiin lambda-suljuksessa;
  • Annetaan kääntäjälle enemmän mahdollisuuksia optimointiin ehdollisilla noexcept; 
  • Huolehditaan ymmärrettävämmästä virhetulosta malleissa eksplisiittisten lambda-arvojen ansiosta. Tämä pakottaa kääntäjän tekemään enemmän tarkistuksia ennen mallifunktion varsinaista kutsumista - tyyppitarkistusvaiheessa. 

Katso lisätietoja luentomateriaalista: 

Meidän vaikutelmamme

Ensimmäinen osallistumisemme C++ Russia -tapahtumaan jäi mieleen intensiivisyydestään. Sain vaikutelman C++ Venäjästä vilpittömänä tapahtumana, jossa treenin ja live-kommunikoinnin välinen raja on lähes huomaamaton. Kaikki, puhujien tunnelmasta tapahtumakumppaneiden kilpailuihin, edistää kiivasta keskustelua. Raporteista koostuvan konferenssin sisältö kattaa melko laajan aihealueen, mukaan lukien C++-innovaatiot, isojen projektien tapaustutkimukset ja ideologiset arkkitehtoniset näkökohdat. Mutta olisi epäreilua jättää huomiotta tapahtuman sosiaalinen osa, joka auttaa ylittämään kielimuurit paitsi C++:ssa.

Kiitämme konferenssin järjestäjiä mahdollisuudesta osallistua tällaiseen tapahtumaan!
Olet ehkä nähnyt järjestäjien postauksen C++ Venäjän menneisyydestä, nykyisyydestä ja tulevaisuudesta JUG Ru -blogissa.

Kiitos, että luit, ja toivomme, että tapahtumien uudelleenkertomuksestamme oli apua!

Lähde: will.com

Lisää kommentti