Wenn Sie zu Beginn des Stücks sagen, dass C++-Code an der Wand hängt, wird es Ihnen am Ende bestimmt in den Fuß schießen.
Bjarne Stroustrup
Vom 31. Oktober bis 1. November fand in St. Petersburg die C++ Russia Piter-Konferenz statt – eine der größten Programmierkonferenzen in Russland, organisiert von der JUG Ru Group. Zu den eingeladenen Rednern gehören Mitglieder des C++ Standards Committee, CppCon-Redner, O'Reilly-Buchautoren und Betreuer von Projekten wie LLVM, libc++ und Boost. Die Konferenz richtet sich an erfahrene C++-Entwickler, die ihr Fachwissen vertiefen und Erfahrungen in der Live-Kommunikation austauschen möchten. Studenten, Doktoranden und Hochschullehrer erhalten sehr attraktive Rabatte.
Die Moskauer Ausgabe der Konferenz wird bereits im April nächsten Jahres zu besichtigen sein, aber in der Zwischenzeit werden Ihnen unsere Studenten erzählen, welche interessanten Dinge sie bei der letzten Veranstaltung gelernt haben.
Zwei Studenten der National Research University Higher School of Economics – St. Petersburg haben an diesem Beitrag mitgearbeitet:
Liza Vasilenko studiert im vierten Studienjahr Programmiersprachen im Rahmen des Studiengangs Angewandte Mathematik und Informatik. Nachdem ich die Sprache C++ bereits in meinem ersten Studienjahr kennengelernt hatte, sammelte ich anschließend durch Praktika in der Industrie Erfahrungen im Umgang damit. Meine Leidenschaft für Programmiersprachen im Allgemeinen und funktionale Programmierung im Besonderen hat die Auswahl der Berichte auf der Konferenz geprägt.
Danya Smirnov ist Studentin im ersten Jahr des Masterstudiengangs „Programmierung und Datenanalyse“. Noch während meiner Schulzeit habe ich Olympiade-Aufgaben in C++ geschrieben, und dann kam es irgendwie vor, dass die Sprache immer wieder in Bildungsaktivitäten auftauchte und schließlich zur Hauptarbeitssprache wurde. Ich habe mich entschieden, an der Konferenz teilzunehmen, um mein Wissen zu verbessern und neue Möglichkeiten kennenzulernen.
Im Newsletter informiert die Fakultätsleitung häufig über Bildungsveranstaltungen rund um unser Fachgebiet. Im September sahen wir Informationen über C++ Russland und beschlossen, uns als Zuhörer zu registrieren. Dies ist unsere erste Erfahrung mit der Teilnahme an solchen Konferenzen.
Konferenzstruktur
Доклады
Innerhalb von zwei Tagen lasen Experten 30 Berichte zu vielen aktuellen Themen: geniale Nutzung von Sprachfunktionen zur Lösung angewandter Probleme, bevorstehende Sprachaktualisierungen im Zusammenhang mit dem neuen Standard, Kompromisse im C++-Design und Vorsichtsmaßnahmen bei der Arbeit mit deren Konsequenzen, Beispiele einer interessanten Projektarchitektur sowie einige Details der Sprachinfrastruktur unter der Haube. Drei Aufführungen fanden gleichzeitig statt, meist zwei auf Russisch und eine auf Englisch.
Diskussionszonen
Nach der Rede wurden alle offenen Fragen und nicht abgeschlossenen Diskussionen in speziell dafür vorgesehene Bereiche zur Kommunikation mit den Rednern verlegt, die mit Markierungstafeln ausgestattet waren. Eine gute Möglichkeit, die Pause zwischen den Reden mit einem angenehmen Gespräch zu überbrücken.
Lightning Talks und informelle Diskussionen
Wenn Sie einen kurzen Bericht halten möchten, können Sie sich am Whiteboard für den abendlichen Lightning Talk anmelden und erhalten fünf Minuten Zeit, um über alles zum Konferenzthema zu sprechen. Zum Beispiel eine kurze Einführung in Sanitizer für C++ (für einige war es neu) oder eine Geschichte über einen Fehler in der Sinuswellenerzeugung, der nur zu hören, aber nicht zu sehen ist.
Ein weiteres Format ist die Podiumsdiskussion „With a Heart to Heart Committee“. Auf der Bühne stehen einige Mitglieder des Standardisierungsausschusses, auf dem Projektor steht ein Kamin (offiziell – um eine aufrichtige Atmosphäre zu schaffen, aber der Grund „weil ALLES IN FEUER IST“ scheint lustiger), Fragen zum Standard und zur allgemeinen Vision von C++ , ohne hitzige Fachdiskussionen und Feiertage. Es stellte sich heraus, dass dem Komitee auch lebende Menschen angehören, die sich möglicherweise nicht ganz sicher sind oder etwas nicht wissen.
Für Holivar-Fans blieb die dritte Veranstaltung zum Thema – die BOF-Sitzung „Go vs. C++“. Wir nehmen einen Go-Liebhaber, einen C++-Liebhaber, vor Beginn der Sitzung bereiten sie gemeinsam 100500 Folien zu einem Thema vor (z. B. Probleme mit Paketen in C++ oder das Fehlen von Generika in Go), und dann führen sie eine lebhafte Diskussion untereinander und mit dem Publikum, und das Publikum versucht, zwei Standpunkte gleichzeitig zu verstehen. Wenn ein Holivar außerhalb des Kontexts beginnt, greift der Moderator ein und versöhnt die Parteien. Dieses Format macht süchtig: Mehrere Stunden nach dem Start war erst die Hälfte der Folien fertig. Das Ende musste stark beschleunigt werden.
Partner steht
Die Partner der Konferenz waren in den Hallen vertreten – an den Ständen sprachen sie über aktuelle Projekte, boten Praktika und Anstellungen an, veranstalteten Quizze und kleine Wettbewerbe und verlosten auch schöne Preise. Gleichzeitig boten einige Unternehmen sogar an, die ersten Interviews zu durchlaufen, was für diejenigen, die kamen, nicht nur zum Anhören von Berichten nützlich sein könnte.
Technische Details der Berichte
An beiden Tagen haben wir uns Berichte angehört. Manchmal war es schwierig, aus den Parallelberichten einen auszuwählen – wir einigten uns darauf, uns aufzuteilen und in den Pausen die gewonnenen Erkenntnisse auszutauschen. Und dennoch scheint vieles ausgelassen zu werden. Hier möchten wir über den Inhalt einiger der Berichte sprechen, die wir am interessantesten fanden
Ausnahmen in C++ durch das Prisma der Compiler-Optimierungen, Roman Rusyaev
Wie der Titel vermuten lässt, untersuchte Roman die Arbeit mit Ausnahmen am Beispiel von LLVM. Gleichzeitig kann der Bericht denjenigen, die Clang bei ihrer Arbeit nicht verwenden, dennoch eine Vorstellung davon geben, wie der Code möglicherweise optimiert werden könnte. Dies liegt daran, dass die Entwickler von Compilern und entsprechenden Standardbibliotheken miteinander kommunizieren und viele erfolgreiche Lösungen zusammenfallen können.
Um eine Ausnahme zu behandeln, müssen Sie also viele Dinge tun: den Behandlungscode aufrufen (falls vorhanden) oder Ressourcen auf der aktuellen Ebene freigeben und den Stapel höher hochfahren. All dies führt dazu, dass der Compiler zusätzliche Anweisungen für Aufrufe hinzufügt, die möglicherweise Ausnahmen auslösen. Wenn die Ausnahme also nicht tatsächlich ausgelöst wird, führt das Programm dennoch unnötige Aktionen aus. Um den Overhead irgendwie zu reduzieren, verfügt LLVM über mehrere Heuristiken zur Bestimmung von Situationen, in denen kein Ausnahmebehandlungscode hinzugefügt werden muss oder die Anzahl „zusätzlicher“ Anweisungen reduziert werden kann.
Der Referent untersucht etwa ein Dutzend davon und zeigt sowohl Situationen auf, in denen sie zur Beschleunigung der Programmausführung beitragen, als auch solche, in denen diese Methoden nicht anwendbar sind.
Daher führt Roman Rusyaev die Schüler zu dem Schluss, dass Code, der die Ausnahmebehandlung enthält, nicht immer ohne Overhead ausgeführt werden kann, und gibt den folgenden Rat:
Bei der Entwicklung von Bibliotheken lohnt es sich, grundsätzlich auf Ausnahmen zu verzichten.
Wenn weiterhin Ausnahmen erforderlich sind, lohnt es sich, wann immer möglich, überall die Modifikatoren noexclusive (und const) hinzuzufügen, damit der Compiler so viel wie möglich optimieren kann.
Generell bestätigte der Referent die Auffassung, dass Ausnahmen am besten auf ein Minimum ausgeschöpft oder ganz aufgegeben werden sollten.
Einer der vielen Berichte auf dieser Konferenz, die sich mit Innovationen in C++20 befassten, beeindruckte nicht nur durch seine farbenfrohe Präsentation, sondern auch durch die klare Identifizierung bestehender Probleme mit der Sammlungsverarbeitungslogik (for-Schleife, Rückrufe).
Adi Shavit hebt Folgendes hervor: Die derzeit verfügbaren Methoden durchlaufen die gesamte Sammlung und bieten keinen Zugriff auf einen internen Zwischenstatus (oder sie tun dies im Fall von Rückrufen, aber mit vielen unangenehmen Nebenwirkungen, wie z. B. der Callback-Hölle). . Es scheint, dass es Iteratoren gibt, aber selbst mit ihnen läuft nicht alles so reibungslos: Es gibt keine gemeinsamen Ein- und Ausstiegspunkte (begin → end versus rbegin → rend usw.), es ist nicht klar, wie lange wir iterieren werden? Ab C++20 sind diese Probleme gelöst!
Erste Option: Bereiche. Durch das Einschließen von Iteratoren erhalten wir eine gemeinsame Schnittstelle für den Anfang und das Ende einer Iteration und erhalten außerdem die Möglichkeit zum Verfassen. All dies erleichtert den Aufbau vollwertiger Datenverarbeitungspipelines. Aber nicht alles läuft so reibungslos: Ein Teil der Berechnungslogik befindet sich in der Implementierung eines bestimmten Iterators, was das Verstehen und Debuggen des Codes erschweren kann.
Nun, für diesen Fall hat C++20 Coroutinen hinzugefügt (Funktionen, deren Verhalten den Generatoren in Python ähnelt): Die Ausführung kann verzögert werden, indem ein aktueller Wert zurückgegeben wird, während ein Zwischenzustand beibehalten wird. Auf diese Weise können wir nicht nur mit den Daten arbeiten, wie sie erscheinen, sondern auch die gesamte Logik in einer bestimmten Coroutine kapseln.
Aber es gibt einen Wermutstropfen: Derzeit werden sie von bestehenden Compilern nur teilweise unterstützt und sind auch nicht so sauber implementiert, wie wir es gerne hätten: Beispielsweise lohnt es sich noch nicht, Referenzen und temporäre Objekte in Coroutinen zu verwenden. Darüber hinaus gibt es einige Einschränkungen hinsichtlich der möglichen Coroutinen, und constexpr-Funktionen, Konstruktoren/Destruktoren und main sind in dieser Liste nicht enthalten.
Somit lösen Coroutinen einen erheblichen Teil der Probleme durch die Einfachheit der Datenverarbeitungslogik, ihre aktuellen Implementierungen müssen jedoch verbessert werden.
In meiner beruflichen Tätigkeit muss ich manchmal reine Hilfsdinge implementieren: einen Wrapper zwischen der internen Schnittstelle und der API einer Bibliothek, Protokollierung oder Analyse. In diesem Fall ist in der Regel keine zusätzliche Optimierung erforderlich. Was aber, wenn diese Komponenten in einigen der beliebtesten Dienste im RuNet verwendet werden? In einer solchen Situation müssen Sie allein Protokolle im Terabyte-Bereich pro Stunde verarbeiten! Dann zählt jede Millisekunde und deshalb muss man auf verschiedene Tricks zurückgreifen – Anton Polukhin hat darüber gesprochen.
Das vielleicht interessanteste Beispiel war die Implementierung des Pointer-to-Implementation-Musters (Pimpl).
In diesem Beispiel möchte ich zunächst die Header-Dateien externer Bibliotheken entfernen. Dadurch wird die Kompilierung beschleunigt und Sie können sich vor möglichen Namenskonflikten und anderen ähnlichen Fehlern schützen.
Okay, wir haben #include in die .cpp-Datei verschoben: Wir benötigen eine Forward-Deklaration der umschlossenen API sowie std::unique_ptr. Jetzt haben wir dynamische Zuweisungen und andere unangenehme Dinge wie Daten, die über eine Menge Daten verstreut sind, und reduzierte Garantien. std::aligned_storage kann dabei helfen.
Das einzige Problem: Sie müssen die Größe und Ausrichtung für jeden Wrapper angeben – erstellen wir unsere Pimpl-Vorlage mit Parametern , verwenden Sie einige beliebige Werte und überprüfen Sie den Destruktor, ob wir alles richtig gemacht haben:
Da T bei der Verarbeitung des Destruktors bereits definiert ist, wird dieser Code korrekt analysiert und in der Kompilierungsphase werden die erforderlichen Größen- und Ausrichtungswerte ausgegeben, die als Fehler eingegeben werden müssen. Auf diese Weise entledigen wir uns auf Kosten eines zusätzlichen Kompilierungslaufs der dynamischen Zuordnung verpackter Klassen, verstecken die API in einer .cpp-Datei mit der Implementierung und erhalten außerdem ein Design, das sich besser für die Zwischenspeicherung durch den Prozessor eignet.
Protokollierung und Analyse schienen weniger beeindruckend und werden daher in diesem Testbericht nicht erwähnt.
Moderne Techniken, um Ihren Code trocken zu halten, Björn Fahller
In diesem Vortrag zeigt Björn Fahller verschiedene Möglichkeiten auf, dem Stilfehler wiederholter Zustandsprüfungen entgegenzuwirken:
assert(a == IDLE || a == CONNECTED || a == DISCONNECTED);
Klingt bekannt? Durch die Verwendung mehrerer leistungsstarker C++-Techniken, die in neueren Standards eingeführt wurden, können Sie dieselbe Funktionalität elegant und ohne Leistungseinbußen implementieren. Vergleichen:
Um eine nicht festgelegte Anzahl von Prüfungen zu bewältigen, müssen Sie sofort variadische Vorlagen und Faltausdrücke verwenden. Nehmen wir an, wir wollen die Gleichheit mehrerer Variablen mit dem state_type-Element der Aufzählung prüfen. Das erste, was mir in den Sinn kommt, ist, eine Hilfsfunktion is_any_of zu schreiben:
Nicht-typspezifische Vorlagenparameter werden dazu beitragen, die Situation ein wenig zu verbessern. Mit ihrer Hilfe übertragen wir die aufzählbaren Elemente der Aufzählung in die Liste der Template-Parameter:
Durch die Verwendung von auto in einem Nicht-Typ-Vorlagenparameter (C++17) lässt sich der Ansatz einfach auf Vergleiche nicht nur mit state_type-Elementen verallgemeinern, sondern auch mit primitiven Typen, die als Nicht-Typ-Vorlagenparameter verwendet werden können:
In diesem Beispiel dient der Ableitungsleitfaden dazu, dem Compiler, der die Typen der Konstruktorargumente kennt, die gewünschten Strukturvorlagenparameter vorzuschlagen.
Weiter - interessanter. Bjorn lehrt, wie man den resultierenden Code für Vergleichsoperatoren über == hinaus und dann für beliebige Operationen verallgemeinert. Nebenbei werden Features wie das Attribut no_unique_address (C++20) und Template-Parameter in Lambda-Funktionen (C++20) anhand von Anwendungsbeispielen erläutert. (Ja, jetzt ist die Lambda-Syntax noch einfacher zu merken – das sind vier aufeinanderfolgende Klammerpaare aller Art.) Die endgültige Lösung, die Funktionen als Konstruktordetails verwendet, wärmt meine Seele wirklich, ganz zu schweigen vom Ausdruck Tupel in den besten Traditionen von Lambda Infinitesimalrechnung.
Vergessen Sie am Ende nicht, es aufzupolieren:
Denken Sie daran, dass Lambdas kostenlos constexpr sind.
Fügen wir die perfekte Weiterleitung hinzu und schauen uns die hässliche Syntax in Bezug auf das Parameterpaket im Lambda-Abschluss an.
Geben wir dem Compiler mehr Möglichkeiten für Optimierungen mit bedingtem „noexclusive“;
Sorgen wir dank expliziter Rückgabewerte von Lambdas für eine verständlichere Fehlerausgabe in Vorlagen. Dadurch wird der Compiler gezwungen, weitere Prüfungen durchzuführen, bevor die Vorlagenfunktion tatsächlich aufgerufen wird – in der Phase der Typprüfung.
Einzelheiten entnehmen Sie bitte den Vorlesungsmaterialien:
Unsere erste Teilnahme an C++ Russia war wegen ihrer Intensität unvergesslich. Ich hatte den Eindruck, dass C++ Russia eine ernsthafte Veranstaltung war, bei der die Grenze zwischen Schulung und Live-Kommunikation kaum wahrnehmbar ist. Von der Stimmung der Redner bis hin zu den Gewinnspielen der Veranstaltungspartner ist alles für hitzige Diskussionen geeignet. Der Inhalt der Konferenz, bestehend aus Berichten, deckt ein recht breites Themenspektrum ab, darunter C++-Innovationen, Fallstudien großer Projekte und ideologische Architekturüberlegungen. Aber es wäre unfair, die soziale Komponente der Veranstaltung zu ignorieren, die dabei hilft, Sprachbarrieren nicht nur in Bezug auf C++ zu überwinden.
Wir danken den Organisatoren der Konferenz für die Möglichkeit, an einer solchen Veranstaltung teilzunehmen!
Möglicherweise haben Sie den Beitrag der Organisatoren über die Vergangenheit, Gegenwart und Zukunft von C++ Russia gesehen auf dem JUG Ru-Blog.
Vielen Dank fürs Lesen und wir hoffen, dass unsere Nacherzählung der Ereignisse hilfreich war!