C++ Rosja: jak to było

Jeśli na początku sztuki mówisz, że na ścianie wisi kod w C++, to na końcu musi on koniecznie wystrzelić ci w nogę.

Bjarne Stroustrup

Od 31 października do 1 listopada w Petersburgu odbyła się konferencja C++ Rosja Piter – jedna z największych konferencji programistycznych w Rosji, organizowana przez JUG Ru Group. Wśród zaproszonych prelegentów są członkowie komitetu standardyzacyjnego C++, wykładowcy z CppCon, autorzy książek wydawnictwa O’Reilly oraz maintainerzy takich projektów jak LLVM, libc++ i Boost. Konferencja jest skierowana do doświadczonych programistów w C++, którzy pragną pogłębić swoją wiedzę i wymienić doświadczenia w bezpośrednim kontakcie. Studenci, doktoranci i wykładowcy uniwersytetów otrzymują bardzo atrakcyjne zniżki.

Moskiewską edycję konferencji będzie można odwiedzić już w kwietniu przyszłego roku, a tymczasem nasi studenci opowiedzą, co interesującego dowiedzieli się na minionym wydarzeniu. 

C++ Rosja: jak to było

Zdjęcie z albumu konferencji

O nas

Nad tym postem pracowały dwie studentki NIES WSHSE – Petersburg:

  • Liza Wasilienko – studentka 4. roku studiów licencjackich, studiująca kierunek 'Języki programowania' w ramach programu 'Matematyka stosowana i informatyka'. Poznała język C++ na pierwszym roku uniwersytetu, a następnie zdobyła doświadczenie w pracy z nim podczas staży w branży. Zafascynowanie językami programowania w ogóle i programowaniem funkcyjnym w szczególności miało wpływ na wybór wykładów na konferencji.
  • Dania Smirnow – student 1. roku studiów magisterskich 'Programowanie i analiza danych'. Już w szkole pisał zadania olimpijskie w C++, a potem jakoś tak się stało, że język ten ciągle pojawiał się w jego działalności edukacyjnej i ostatecznie stał się głównym językiem roboczym. W konferencji postanowił wziąć udział, aby podnieść swoje umiejętności oraz poznać nowe możliwości.

W biuletynie wydziałowym kierownictwo wydziału często dzieli się informacjami o wydarzeniach edukacyjnych związanych z naszym kierunkiem. We wrześniu zobaczyliśmy informacje o C++ Rosja i postanowiliśmy zarejestrować się jako słuchacze. To nasze pierwsze doświadczenie w uczestnictwie w tego typu konferencjach.

Struktura konferencji

  • Referaty

W ciągu dwóch dni eksperci przeczytali 30 referatów, omawiając wiele gorących tematów: pomysłowe zastosowania funkcji języka do rozwiązywania praktycznych zadań, nadchodzące aktualizacje języka w związku z nowym standardem, kompromisy w projektowaniu C++ oraz środki ostrożności przy pracy z ich konsekwencjami, przykłady interesującej architektury projektów, a także niektóre techniczne szczegóły infrastruktury języka. Równocześnie odbywały się 3 wystąpienia, najczęściej dwa w języku rosyjskim i jedno w angielskim.

  • Strefy dyskusyjne

Po wystąpieniu wszystkie niezadane pytania i niedokończone dyskusje przenosiły się do specjalnie wydzielonych stref komunikacyjnych z prelegentami, wyposażonych w tablice suchościeralne. Dobry sposób na spędzenie przerwy między wystąpieniami w miłej rozmowie.

  • Lightning Talks i nieformalne dyskusje

Jeśli masz ochotę zrobić krótką prezentację — można zapisać się na tablicy suchościeralnej na wieczorny Lightning Talk i otrzymać pięć minut na opowiedzenie o dowolnym temacie związanym z konferencją. Na przykład, szybkie wprowadzenie do sanitizers dla C++ (dla niektórych okazało się to nowością) lub historia o błędzie w generowaniu sinusoidy, który można usłyszeć, ale nie zobaczyć.

Inny format — dyskusja panelowa „Z Komitetem o duszy”. Na scenie — niektórzy członkowie komitetu ds. standaryzacji, na projectorze — kominek (oficjalnie — dla stworzenia przyjemnej atmosfery, lecz powód „bo WSZYSTKO W OGNIU” wydaje się zabawniejszy), pytania — o standard i ogólne spojrzenie na C++, bez burzliwych dyskusji technicznych i kontrowersji. Okazało się, że w komitecie są również żywi ludzie, którzy mogą się w czymś nie do końca upewnić lub czegoś nie wiedzieć.

Dla miłośników kontrowersji na temat pozostał trzeci format — sesja BOF „Go kontra C++”. Bierzemy fana Go, fana C++, przed rozpoczęciem sesji wspólnie przygotowują 100500 slajdów na temat (jakieś problemy z pakietami w C++ lub brak generików w Go), a następnie ożywiona dyskusja między nimi a publicznością, która próbuje zrozumieć od razu dwa punkty widzenia. Jeśli zaczyna się kontrowersja nie na temat — wkracza moderator i godzi strony. Taki format wciąga: kilka godzin po rozpoczęciu przeszliśmy tylko połowę slajdów. Koniec musiano znacznie przyspieszyć.

  • Stoiska partnerów

Na holach zaprezentowali się partnerzy konferencji – na stoiskach opowiadali o bieżących projektach, oferowali staże i zatrudnienie, organizowali quizy i małe zawody, a także rozgrywali atrakcyjne nagrody. Niektóre firmy nawet proponowały przejście przez wstępne etapy rozmów kwalifikacyjnych, co może być przydatne dla tych, którzy przyjechali nie tylko słuchać wystąpień.

Szczegóły techniczne wystąpień

Słuchaliśmy wystąpień przez dwa dni. Czasami trudno było wybrać jedno wystąpienie z równolegle odbywających się – umówiliśmy się na podział i wymianę zdobytej wiedzy w przerwach. Nawet w ten sposób wydaje się, że wiele rzeczy nam umknęło. Chcielibyśmy tutaj opowiedzieć o treści niektórych wystąpień, które uznaliśmy za najbardziej interesujące.

Wyjątki w C++ w kontekście optymalizacji kompilatora, Roman Rusajew

C++ Rosja: jak to było
Slajd z prezentacji

Jak wynika z tytułu, Roman omówił pracę z wyjątkami na przykładzie LLVM. Nawet dla tych, którzy nie korzystają z Clang, wykład może dać pewne pojęcie o tym, jak kod może być potencjalnie optymalizowany. Tak jest, ponieważ programiści kompilatorów i odpowiednich standardowych bibliotek komunikują się ze sobą, a wiele udanych rozwiązań może być zbieżnych.

Zatem, aby obsłużyć wyjątek, należy wykonać wiele czynności: wywołać kod obsługi (jeśli istnieje) lub zwolnić zasoby na bieżącym poziomie i przejść do wyższego stosu. Wszystko to prowadzi do sytuacji, w której dla potencjalnie zgłaszających wyjątki wywołań kompilator dodaje dodatkowe instrukcje. Dlatego, jeśli wyjątek w rzeczywistości nie zostanie zgłoszony, program mimo wszystko będzie wykonywał niepotrzebne działania. Aby w jakiś sposób obniżyć koszty, w LLVM istnieje kilka heurystyk określających sytuacje, w których dodawanie kodu obsługi wyjątków nie jest konieczne lub można zmniejszyć liczbę „zbędnych” instrukcji.

Prelegent omawia około dziesięciu z nich i pokazuje, jak w sytuacjach, w których pomagają przyspieszyć wykonanie programu, jak i w tych, w których te metody nie mają zastosowania.

W ten sposób Roman Rusajew prowadzi słuchaczy do wniosku, że kod zawierający obsługę wyjątków nie zawsze można wykonywać z zerowymi kosztami, i daje następujące wskazówki:

  • Podczas tworzenia bibliotek warto całkowicie zrezygnować z wyjątków.
  • Jeśli jednak wyjątki są potrzebne, należy dodać modyfikatory noexcept (i const) wszędzie, gdzie to możliwe, aby kompilator mógł zoptymalizować, jak najwięcej.

Ogólnie rzecz biorąc, prelegent potwierdził opinię, że wyjątki najlepiej stosować w minimalnych ilościach lub całkowicie się ich wystrzegać.

Slajdy z referatu są dostępne pod linkiem: [„Wyjątki C++ w świetle optymalizacji kompilatora LLVM”]

Generatory, korutyny i inne wspaniałości, Adi Shavit

C++ Rosja: jak to było
Slajd z prezentacji

Jedna z wielu prezentacji na tej konferencji poświęconych nowościom C++20 zapadła w pamięć nie tylko efektowną prezentacją, ale także wyraźnym wskazaniem istniejących problemów z logiką przetwarzania kolekcji (pętla for, callbacki).

Adi Shavit wyróżnia następujące: obecne metody przechodzą przez całą kolekcję i nie dają dostępu do pewnego wewnętrznego stanu pośredniego (lub dają w przypadku callbacków, ale z wieloma nieprzyjemnymi skutkami ubocznymi, takimi jak Callback Hell). Można by pomyśleć, że są iteratory, ale wcale nie jest tak gładko: nie ma wspólnych punktów wejścia i wyjścia (begin → end w porównaniu do rbegin → rend itd.), nie wiadomo, jak długo będziemy iterować? Od C++20 te problemy zostaną rozwiązane!

Pierwsza opcja: zakresy. Dzięki owinięciu nad iteratorami uzyskujemy wspólny interfejs do rozpoczęcia i zakończenia iteracji, a także możliwość kompozycji. To wszystko pozwala łatwo budować pełnoprawne potoki przetwarzania danych. Ale nie wszystko jest takie gładkie: część logiki obliczeń znajduje się wewnątrz implementacji konkretnego iteratora, co może skomplikować kod do zrozumienia i debugowania.

C++ Rosja: jak to było
Slajd z prezentacji

Cóż, w takim razie w C++20 dodano korutyny (funkcje, których zachowanie przypomina generatory w języku Python): wykonanie można opóźnić, zwracając pewną bieżącą wartość z zachowaniem stanu pośredniego. W ten sposób osiągamy nie tylko przetwarzanie danych w miarę ich pojawiania się, ale także inkapsulujemy całą logikę wewnątrz konkretnej korutyny.

Ale jest mały szkopuł: na obecną chwilę są one jedynie częściowo wspierane przez dostępne kompilatory, a także zaimplementowane nieco niedbale, niż byśmy chcieli: na przykład, na razie nie powinno się używać odniesień i obiektów tymczasowych w korutynach. Ponadto istnieją pewne ograniczenia co do tego, co może być korutyną, a funkcje constexpr, konstruktory/destruktory oraz main do tej listy nie wchodzą.

W ten sposób korutyny rozwiązują znaczną część problemów z prostotą logiki przetwarzania danych, ale ich obecne implementacje wymagają dopracowania.

Materiały:

Triki C++ z Yandex.Taxi, Anton Poluchin

W swojej działalności zawodowej czasami muszę wdrażać czysto pomocnicze rzeczy: wrapper pomiędzy wewnętrznym interfejsem a API jakiejś biblioteki, logowanie czy parsowanie. Przy tym zazwyczaj nie ma potrzeby dodatkowej optymalizacji. Ale co, jeśli te komponenty są używane w jednym z najpopularniejszych serwisów w RuNet? W takiej sytuacji trzeba będzie przetwarzać terabajty na godzinę z samych logów! Wtedy każda milisekunda jest na wagę złota i dlatego trzeba sięgać po różne triki — o tym opowiadał Anton Poluchin.

Z pewnością najciekawszym przykładem była implementacja wzorca pointer-to-implementation (pimpl). 

#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_; 
};

W tym przykładzie na początku chcemy pozbyć się plików nagłówkowych zewnętrznych bibliotek — dzięki temu kompilacja będzie szybsza, a także zabezpieczymy się przed możliwymi konfliktami nazw oraz innymi podobnymi błędami. 

Dobrze, przenieśliśmy #include do pliku .cpp: potrzebne jest forward-declaration owijanego API, a także std::unique_ptr. Teraz mamy dynamiczne alokacje i inne nieprzyjemne rzeczy, takie jak rozproszone po stosie dane oraz zmniejszone gwarancje. W tym może pomóc std::aligned_storage. 

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 data_;
};

Jedyny problem: trzeba dla każdego wrappera określić rozmiar i wyrównanie — zrobimy nasz pimpl szablonowym z parametrami , użyjemy jakichś dowolnych wartości i dodamy do destruktora sprawdzenie, czy wszystko się zgadza: 

~FastPimpl() noexcept { 
    validate(); 
    Ptr()->~T(); 
}

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

Ponieważ przy przetwarzaniu destruktora T jest już zdefiniowany, kod ten będzie poprawnie analizowany na etapie kompilacji, a w postaci błędów zwróci odpowiednie wartości rozmiaru i wyrównania, które należy wpisać. W ten sposób, kosztem jednego dodatkowego uruchomienia kompilacji, pozbywamy się dynamicznej alokacji klas owijających, ukrywamy interfejs API w pliku .cpp z implementacją, a także uzyskujemy bardziej przyjazną dla pamięci podręcznej konstrukcję.

Logowanie i parsowanie wydają się mniej imponujące, dlatego w tej recenzji nie będą wspomniane.

Slajdy z referatu są dostępne pod linkiem: [„C++ triki z Taksi”]

Nowoczesne techniki pozwalające utrzymać kod w stylu DRY, Björn Fahller

W tym wykładzie Björn Fahller pokazuje kilka różnych sposobów radzenia sobie z takim stylistycznym niedociągnięciem, jak powtarzające się sprawdzenia warunków:

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

Znajome? Używając kilku potężnych technik C++, które pojawiły się w niedawnych standardach, można elegancko zaimplementować tę samą funkcjonalność bez najmniejszych strat w wydajności. Porównaj:   

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

Aby obsłużyć nieokreśloną liczbę sprawdzeń, warto wykorzystać szablony zmiennoargumentowe oraz fold expressions. Załóżmy, że chcemy sprawdzić równość kilku zmiennych z elementem typu enum state_type. Pierwsza myśl to napisanie funkcji pomocniczej is_any_of:


enum state_type { IDLE, CONNECTED, DISCONNECTED };

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

Taki pośredni wynik wywołuje rozczarowanie. Jak dotąd kod nie staje się bardziej czytelny:

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

Nieco poprawić sytuację mogą parametry szablonowe non-type. Dzięki nim przeniesiemy elementy enum do listy parametrów szablonu: 

template 
bool is_any_of(state_type t) { 
    return ((t == states) | ...); 
}
	
assert(is_any_of(state)); 

Z użyciem auto w parametrze szablonu non-type (C++17), podejście to prosto uogólnia porównania nie tylko z elementami state_type, ale także z typami prymitywnymi, które można wykorzystać jako parametry szablonowe non-type:


template 
bool is_any_of(const T& t) {
    return ((t == alternatives) | ...);
}

Dzięki takim sekwencyjnym ulepszeniom osiągany jest pożądany płynny składnia do sprawdzeń:


template 
struct any_of : private std::tuple { 
// nie lenić się, odziedziczmy konstruktory od tuple 
        using std::tuple::tuple;
        template 
        bool operator ==(const T& t) const {
                return std::apply(
                        [&t](const auto& ... ts) {
                                return ((ts == t) || ...);
                        },
                        static_cast<const std::tuple&>(*this));
        }
};

template 
any_of(Ts ...) -> any_of;

assert(any_of(IDLE, DISCONNECTING, DISCONNECTED) == state);

W tym przykładzie dedukcyjna wskazówka służy do zasugerowania pożądanych parametrów szablonów struktury kompilatorowi, który zna typy argumentów konstruktora. 

Dalej będzie ciekawiej. Bjorn uczy, jak uogólnić powstały kod dla operatorów porównania, poza ==, a następnie i dla dowolnych operacji. Przy okazji na przykładzie użycia wyjaśniane są takie funkcje, jak atrybut no_unique_address (C++20) oraz parametry szablonów w funkcjach lambda (C++20). (Tak, teraz składnia lambd jest jeszcze łatwiejsza do zapamiętania – to cztery kolejne pary nawiasów wszystkich rodzajów.) Ostateczne rozwiązanie z użyciem funkcji jako detali konstruktora bardzo mnie cieszy, nie mówiąc już o wyrażeniu tuple w najlepszej tradycji rachunku lambda.

Na koniec nie zapominamy o dopracowaniu:

  • Przypomnijmy, że lambdy – constexpr za darmo; 
  • Dodamy perfect forwarding i przyjrzymy się jego brzydkiej składni w odniesieniu do parameter pack w zamknięciach lambd;
  • Dajemy kompilatorowi więcej możliwości optymalizacji z conditional noexcept; 
  • Zatroszczymy się o bardziej zrozumiały wynik błędów w szablonach dzięki jawnie określanym wartościom zwracanym przez lambdy. To zmusi kompilator do wykonania większej liczby sprawdzeń przed właściwym wywołaniem funkcji szablonowej – na etapie sprawdzania typów. 

Szczegóły znajdziesz w materiałach wykładu: 

Nasze wrażenia

Nasze pierwsze uczestnictwo w C++ Russia zapadło w pamięć z powodu swojej intensywności. Stworzyło to przekonanie, że C++ Russia to wydarzenie pełne emocji, gdzie granica między nauką a żywym kontaktem jest niemal niezauważalna. Wszystko, od nastroju prelegentów po konkursy organizowane przez partnerów wydarzenia, sprzyja żywym dyskusjom. Część merytoryczna konferencji, obejmująca wykłady, porusza szeroki wachlarz tematów, w tym innowacje w C++, przykłady z praktyki dużych projektów oraz ideologiczne rozważania architektoniczne. Nie byłoby jednak sprawiedliwe pominąć także społeczny aspekt wydarzenia, który sprzyja przełamywaniu barier językowych nie tylko w kontekście C++.

Dziękujemy organizatorom konferencji za możliwość uczestniczenia w takim wydarzeniu!
Post organizatorów o przeszłości, teraźniejszości i przyszłości C++ Russia mogliście zobaczyć na blogu JUG Ru.

Dziękujemy za przeczytanie i mamy nadzieję, że nasza relacja z wydarzenia była pomocna!

Źródło: habr.com

Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS 🔥 Kup solidny hosting stron z ochroną przed DDoS, serwery VPS VDS | ProHoster