C++ Russia: come è successo

Se all'inizio del gioco dici che c'è del codice C++ appeso al muro, alla fine è destinato a darti la zappa sui piedi.

Bjarne Stroustrup

Dal 31 ottobre al 1 novembre si è tenuta a San Pietroburgo la conferenza C++ Russia Piter, una delle conferenze di programmazione su larga scala in Russia, organizzata da JUG Ru Group. I relatori ospiti includono membri del Comitato per gli standard C++, relatori CppCon, autori di libri di O'Reilly e manutentori di progetti come LLVM, libc++ e Boost. La conferenza è rivolta a sviluppatori C++ esperti che desiderano approfondire le proprie competenze e scambiare esperienze nella comunicazione dal vivo. Studenti, laureati e docenti universitari usufruiscono di sconti molto vantaggiosi.

L'edizione moscovita della conferenza sarà visitabile già ad aprile del prossimo anno, ma nel frattempo i nostri studenti vi racconteranno quali cose interessanti hanno imparato durante l'ultimo evento. 

C++ Russia: come è successo

Foto di album del convegno

Chi siamo

Due studenti della Scuola Superiore di Economia della National Research University - San Pietroburgo hanno lavorato a questo post:

  • Liza Vasilenko è una studentessa universitaria del 4° anno che studia Linguaggi di programmazione nell'ambito del programma di Matematica applicata e Informatica. Avendo conosciuto il linguaggio C++ già nel primo anno di università, ho successivamente acquisito esperienza attraverso stage nel settore. La mia passione per i linguaggi di programmazione in generale e per la programmazione funzionale in particolare ha lasciato il segno nella selezione delle relazioni al convegno.
  • Danya Smirnov è una studentessa del 1° anno del programma del master "Programmazione e analisi dei dati". Mentre ero ancora a scuola, ho scritto i problemi delle Olimpiadi in C++, e poi in qualche modo è successo che la lingua comparisse costantemente nelle attività educative e alla fine diventasse la principale lingua di lavoro. Ho deciso di partecipare al convegno per migliorare le mie conoscenze e conoscere anche nuove opportunità.

Nella newsletter, la dirigenza della facoltà condivide spesso informazioni sugli eventi formativi relativi alla nostra specialità. A settembre abbiamo visto informazioni su C++ Russia e abbiamo deciso di registrarci come ascoltatori. Questa è la nostra prima esperienza di partecipazione a tali conferenze.

Struttura della conferenza

  • Доклады

Nel corso di due giorni gli esperti hanno letto 30 relazioni, che coprono molti argomenti caldi: usi ingegnosi delle funzionalità del linguaggio per risolvere problemi applicati, prossimi aggiornamenti del linguaggio in relazione al nuovo standard, compromessi nella progettazione C++ e precauzioni quando si lavora con le loro conseguenze, esempi di un'architettura di progetto interessante, nonché alcuni dettagli nascosti dell'infrastruttura linguistica. Si sono svolte tre rappresentazioni contemporaneamente, molto spesso due in russo e una in inglese.

  • Zone di discussione

Dopo il discorso, tutte le domande non poste e le discussioni non concluse sono state trasferite in aree appositamente designate per la comunicazione con i relatori, dotate di tabelloni segnaletici. Un buon modo per allietare la pausa tra un discorso e l'altro con una piacevole conversazione.

  • Discorsi fulminei e discussioni informali

Se vuoi fare una breve relazione, puoi iscriverti alla lavagna per il Lightning Talk serale e avere cinque minuti di tempo per parlare di qualsiasi cosa riguardante l'argomento della conferenza. Ad esempio, una rapida introduzione ai disinfettanti per C++ (per alcuni era una novità) o una storia su un bug nella generazione di onde sinusoidali che può solo essere ascoltato, ma non visto.

Un altro formato è la tavola rotonda “Con un comitato cuore a cuore”. Sul palco ci sono alcuni membri del comitato di standardizzazione, sul proiettore c'è un caminetto (ufficialmente - per creare un'atmosfera sincera, ma il motivo “perché TUTTO È IN FUOCO” sembra più divertente), domande sullo standard e sulla visione generale del C++ , senza accese discussioni tecniche e holiwar. Si è scoperto che nel comitato sono presenti anche persone viventi che potrebbero non essere completamente sicure di qualcosa o potrebbero non sapere qualcosa.

Per gli appassionati di holivar, il terzo evento è rimasto in sospeso: la sessione BOF "Go vs. C++". Prendiamo un amante del Go, un amante del C++, prima dell'inizio della sessione preparano insieme 100500 diapositive su un argomento (come i problemi con i pacchetti in C++ o la mancanza di generici in Go), e poi discutono animatamente tra loro e con il pubblico, e il pubblico cerca di comprendere due punti di vista contemporaneamente. Se un holivar inizia fuori contesto, il moderatore interviene e riconcilia le parti. Questo formato crea dipendenza: diverse ore dopo l'inizio, solo la metà delle diapositive era completata. La fine doveva essere notevolmente accelerata.

  • Il partner sta in piedi

I partner del convegno erano rappresentati nei padiglioni: negli stand hanno parlato di progetti attuali, hanno offerto stage e lavoro, hanno tenuto quiz e piccoli concorsi e hanno anche messo in palio bei premi. Allo stesso tempo, alcune aziende si sono offerte anche di seguire le fasi iniziali delle interviste, il che potrebbe essere utile per chi è venuto non solo per ascoltare i resoconti.

Dettagli tecnici dei rapporti

Abbiamo ascoltato i resoconti entrambi i giorni. A volte è stato difficile scegliere un rapporto tra quelli paralleli: abbiamo deciso di dividerci e scambiare le conoscenze acquisite durante le pause. E anche così, sembra che molto sia lasciato fuori. Qui vorremmo parlare dei contenuti di alcuni dei report che abbiamo trovato più interessanti

Eccezioni in C++ attraverso il prisma delle ottimizzazioni del compilatore, Roman Rusyaev

C++ Russia: come è successo
Scorri da presentazioni

Come suggerisce il titolo, Roman ha cercato di lavorare con le eccezioni utilizzando LLVM come esempio. Allo stesso tempo, per chi non utilizza Clang nel proprio lavoro, il report può comunque dare qualche idea di come il codice potrebbe essere potenzialmente ottimizzato. Questo perché gli sviluppatori dei compilatori e delle corrispondenti librerie standard comunicano tra loro e molte soluzioni di successo possono coincidere.

Quindi, per gestire un'eccezione, devi fare molte cose: chiamare il codice di gestione (se presente) o liberare risorse al livello corrente e far girare lo stack più in alto. Tutto ciò porta al fatto che il compilatore aggiunge istruzioni aggiuntive per le chiamate che potenzialmente generano eccezioni. Pertanto, se l'eccezione non viene effettivamente sollevata, il programma eseguirà comunque azioni non necessarie. Per ridurre in qualche modo il sovraccarico, LLVM dispone di diverse euristiche per determinare le situazioni in cui non è necessario aggiungere codice di gestione delle eccezioni o è possibile ridurre il numero di istruzioni "extra".

Il relatore ne esamina circa una dozzina e mostra sia le situazioni in cui aiutano a velocizzare l'esecuzione del programma, sia quelle in cui questi metodi non sono applicabili.

Pertanto, Roman Rusyaev porta gli studenti alla conclusione che il codice contenente la gestione delle eccezioni non può sempre essere eseguito con zero spese generali e fornisce il seguente consiglio:

  • quando si sviluppano le librerie, in linea di principio vale la pena abbandonare le eccezioni;
  • se le eccezioni sono ancora necessarie, quando possibile vale la pena aggiungere i modificatori noException (e const) ovunque in modo che il compilatore possa ottimizzare il più possibile.

In generale, l'oratore ha confermato l'opinione secondo cui è meglio sfruttare al minimo le eccezioni o abbandonarle del tutto.

Le slide del report sono disponibili al seguente link: ["Eccezioni C++ attraverso la lente delle ottimizzazioni del compilatore LLVM"]

Generatori, coroutine e altre dolcezze che srotolano il cervello, Adi Shavit

C++ Russia: come è successo
Scorri da presentazioni

Uno dei tanti resoconti di questa conferenza dedicata alle innovazioni in C++20 è stato memorabile non solo per la sua presentazione colorata, ma anche per la chiara identificazione dei problemi esistenti con la logica di elaborazione della raccolta (ciclo for, callback).

Adi Shavit sottolinea quanto segue: i metodi attualmente disponibili attraversano l'intera raccolta e non forniscono accesso a qualche stato intermedio interno (o lo fanno nel caso dei callback, ma con un gran numero di spiacevoli effetti collaterali, come Callback Hell) . Sembrerebbe che ci siano iteratori, ma anche con loro non tutto è così fluido: non ci sono punti di ingresso e di uscita comuni (inizio → fine contro rbegin → rend e così via), non è chiaro per quanto tempo iteraremo? A partire da C++20, questi problemi sono risolti!

Prima opzione: intervalli. Avvolgendo gli iteratori, otteniamo un'interfaccia comune per l'inizio e la fine di un'iterazione e otteniamo anche la possibilità di comporre. Tutto ciò semplifica la creazione di pipeline di elaborazione dati complete. Ma non tutto è così fluido: parte della logica di calcolo si trova all'interno dell'implementazione di un iteratore specifico, il che può complicare la comprensione e il debug del codice.

C++ Russia: come è successo
Scorri da presentazioni

Ebbene, in questo caso, C++20 ha aggiunto le coroutine (funzioni il cui comportamento è simile ai generatori in Python): l'esecuzione può essere rinviata restituendo un valore corrente preservando uno stato intermedio. In questo modo riusciamo non solo a lavorare con i dati così come appaiono, ma anche a incapsulare tutta la logica all'interno di una coroutine specifica.

Ma c'è un neo: per il momento sono supportati solo parzialmente dai compilatori esistenti e non sono nemmeno implementati in modo così preciso come vorremmo: ad esempio, non vale ancora la pena utilizzare riferimenti e oggetti temporanei nelle coroutine. Inoltre, ci sono alcune restrizioni su cosa possono essere coroutine e le funzioni constexpr, costruttori/distruttori e main non sono incluse in questo elenco.

Pertanto, le coroutine risolvono una parte significativa dei problemi con la semplicità della logica di elaborazione dei dati, ma le loro attuali implementazioni richiedono miglioramenti.

materiali:

Trucchi C++ da Yandex.Taxi, Anton Polukhin

Nella mia attività professionale, a volte devo implementare cose puramente ausiliarie: un wrapper tra l'interfaccia interna e l'API di qualche libreria, logging o parsing. In questo caso, di solito non è necessaria alcuna ottimizzazione aggiuntiva. Ma cosa succederebbe se questi componenti venissero utilizzati in alcuni dei servizi più popolari su RuNet? In una situazione del genere, dovrai elaborare solo terabyte di log all'ora! Poi ogni millisecondo conta e quindi bisogna ricorrere a vari trucchi - ne ha parlato Anton Polukhin.

Forse l'esempio più interessante è stata l'implementazione del pattern 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_; 
};

In questo esempio, per prima cosa voglio eliminare i file header delle librerie esterne: questo verrà compilato più velocemente e potrai proteggerti da possibili conflitti di nomi e altri errori simili. 

Ok, abbiamo spostato #include nel file .cpp: abbiamo bisogno di una dichiarazione anticipata dell'API incapsulata, nonché di std::unique_ptr. Ora abbiamo allocazioni dinamiche e altre cose spiacevoli come dati sparsi in un mucchio di dati e garanzie ridotte. std::aligned_storage può aiutare con tutto questo. 

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

L'unico problema: devi specificare la dimensione e l'allineamento per ciascun wrapper: creiamo il nostro modello pimpl con parametri , usa alcuni valori arbitrari e aggiungi un controllo al distruttore per verificare che tutto sia corretto: 

~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"
    ); 
}

Poiché T è già definito durante l'elaborazione del distruttore, questo codice verrà analizzato correttamente e in fase di compilazione genererà i valori di dimensione e allineamento richiesti che devono essere inseriti come errori. Pertanto, al costo di un'ulteriore esecuzione della compilazione, eliminiamo l'allocazione dinamica delle classi incapsulate, nascondiamo l'API in un file .cpp con l'implementazione e otteniamo anche un design più adatto alla memorizzazione nella cache da parte del processore.

La registrazione e l'analisi sembravano meno impressionanti e pertanto non verranno menzionate in questa recensione.

Le slide del report sono disponibili al seguente link: ["Trucchi C++ da Taxi"]

Tecniche moderne per mantenere il codice ASCIUTTO, Björn Fahller

In questo discorso, Björn Fahller mostra diversi modi per combattere il difetto stilistico dei controlli ripetuti delle condizioni:

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

Suona familiare? Utilizzando diverse potenti tecniche C++ introdotte negli standard recenti, è possibile implementare con eleganza la stessa funzionalità senza alcuna penalizzazione delle prestazioni. Confrontare:   

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

Per gestire un numero non fisso di controlli, è necessario utilizzare immediatamente modelli variadici ed espressioni di piegatura. Supponiamo di voler verificare l'uguaglianza di diverse variabili con l'elemento state_type dell'enum. La prima cosa che mi viene in mente è scrivere una funzione helper 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) || ...); 
}

Questo risultato intermedio è deludente. Finora il codice non sta diventando più leggibile:

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

I parametri del modello non di tipo contribuiranno a migliorare un po' la situazione. Con il loro aiuto trasferiremo gli elementi enumerabili dell'enum nella lista dei parametri del template: 

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

Utilizzando auto in un parametro di modello non di tipo (C++17), l'approccio si generalizza semplicemente ai confronti non solo con gli elementi state_type, ma anche con tipi primitivi che possono essere utilizzati come parametri di modello non di tipo:


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

Attraverso questi successivi miglioramenti si ottiene la sintassi fluente desiderata per i controlli:


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);

In questo esempio, la guida alla deduzione serve a suggerire i parametri del modello di struttura desiderati al compilatore, che conosce i tipi degli argomenti del costruttore. 

Inoltre - più interessante. Bjorn insegna come generalizzare il codice risultante per gli operatori di confronto oltre == e quindi per operazioni arbitrarie. Lungo il percorso, funzionalità come l'attributo no_unique_address (C++20) e i parametri del modello nelle funzioni lambda (C++20) vengono spiegate utilizzando esempi di utilizzo. (Sì, ora la sintassi lambda è ancora più facile da ricordare: si tratta di quattro coppie consecutive di parentesi di tutti i tipi.) La soluzione finale che utilizza le funzioni come dettagli del costruttore mi scalda davvero l'anima, per non parlare dell'espressione tuple nella migliore tradizione lambda calcolo.

Alla fine, non dimenticare di perfezionarlo:

  • Ricorda che i lambda sono constexpr gratuiti; 
  • Aggiungiamo l'inoltro perfetto e osserviamo la sua brutta sintassi in relazione al pacchetto di parametri nella chiusura lambda;
  • Diamo al compilatore maggiori opportunità di ottimizzazione con il condizionale noexcept; 
  • Prendiamoci cura di un output degli errori più comprensibile nei modelli grazie ai valori restituiti espliciti di lambda. Ciò costringerà il compilatore a eseguire più controlli prima che la funzione template venga effettivamente chiamata, nella fase di controllo del tipo. 

Per i dettagli si rimanda ai materiali delle lezioni: 

Le nostre impressioni

La nostra prima partecipazione a C++ Russia è stata memorabile per la sua intensità. Ho avuto l'impressione di C++ Russia come di un evento sincero, dove il confine tra formazione e comunicazione dal vivo è quasi impercettibile. Tutto, dall'umore dei relatori alle gare dei partner dell'evento, favorisce accese discussioni. Il contenuto della conferenza, composto da relazioni, copre una gamma abbastanza ampia di argomenti tra cui innovazioni C++, casi di studio di grandi progetti e considerazioni ideologiche sull'architettura. Ma sarebbe ingiusto ignorare la componente sociale dell'evento, che aiuta a superare le barriere linguistiche in relazione non solo al C++.

Ringraziamo gli organizzatori della conferenza per l'opportunità di partecipare a un evento del genere!
Potresti aver visto il post degli organizzatori sul passato, presente e futuro di C++ Russia sul blog JUG Ru.

Grazie per aver letto e speriamo che il nostro racconto degli eventi sia stato utile!

Fonte: habr.com

Aggiungi un commento