Best Practices. Rainer Grimm Training, Coaching und Technologieberatung

Ähnliche Dokumente
Best Practices. Rainer Grimm Training, Coaching und Technologieberatung

Multithreading, richtig gemacht? Rainer Grimm

Das C++- Speichermodell. Rainer Grimm Schulungen, Coaching und Technologieberatung

C++ atomics, Boost.Lookfree, Hazard-Pointers und die Thread-Hölle

Datenstrukturen und Algorithmen

Funktionale Programmierung mit modernem C++ Rainer Grimm Schulungen, Coaching und Technologieberatung

Funktionale Programmierung mit modernem C++ Rainer Grimm Schulungen, Coaching und Technologieberatung

15 Tipps oder warum es nur 10 wurden. Rainer Grimm Schulungen, Coaching und Technologieberatung

Operating Systems Principles C11

PThreads. Pthreads. Jeder Hersteller hatte eine eigene Implementierung von Threads oder light weight processes

Prozesse und Threads. wissen leben WWU Münster

Informatik - Übungsstunde

Programmiertechnik. Teil 4. C++ Funktionen: Prototypen Overloading Parameter. C++ Funktionen: Eigenschaften

15 Tipps oder warum es nur 10 wurden. Rainer Grimm Schulungen, Coaching und Technologieberatung

Lambda-Funktionen. Lambda-Funktionen. Lambda-Funktionen sollen

Multi-Threading wie in C++11

Datenstrukturen und Algorithmen

Lehrstuhl für Datenverarbeitung. Technische Universität München. Leistungskurs C++ Multithreading

Programmierung und Angewandte Mathematik

Multithreading. Paralleles Hello World. int main() { auto hello = [](auto tnum) { cout << "Thread " << tnum << " sagt Hello World\n"; };

Algorithmen und Datenstrukturen

Übung HP Angabe: #include <iostream> #include <vector> #include <locale>

I Grundlagen der parallelen Programmierung 1

Typ : void* aktuelle Parameter Pointer von beliebigem Typ

Nachname: Vorname: Matr.-Nr.: Punkte: 1. Aufgabe: ( / 25 Pkt.) Gegeben ist das folgende Struktogramm zur Berechnung von sin(x) mit Hilfe einer Reihe.

Konstruktor/Destruktor

C++ Teil 12. Sven Groß. 18. Jan Sven Groß (IGPM, RWTH Aachen) C++ Teil Jan / 11

Pthreads. David Klaftenegger. Seminar: Multicore Programmierung Sommersemester

Lehrstuhl für Datenverarbeitung. Technische Universität München. Leistungskurs C++ Multithreading

Repetitorium Programmieren I + II

Einführung in die Programmierung Wintersemester 2016/17

Lehrstuhl für Datenverarbeitung. Technische Universität München. Leistungskurs C++ Multithreading

Einführung in die Programmiersprache C

Praxisorientierte Einführung in C++ Lektion: "Virtuelle Methoden"

Threads. Foliensatz 8: Threads Folie 1. Hans-Georg Eßer, TH Nürnberg Systemprogrammierung, Sommersemester 2015

Embedded Programmierung, die Domäne von C++?

Zeiger vom Typ void* benötigen weniger Speicher als andere Zeiger, da bei anderen Zeigertypen zusätzlich die Größe gespeichert werden muss.

Container für eine Folge gleichartiger Daten. Dynamische Datentypen. Probleme mit Feldern...aber nicht mit std::vector. ... und auch std::vector

Zeiger und dynamischer Speicher

Programmierkurs. Steffen Müthing. November 16, Interdisciplinary Center for Scientific Computing, Heidelberg University

Datenstrukturen und Algorithmen

Programmierkurs C/C++

Praxis der Programmierung

28. Parallel Programming II

Homogene Multi-Core-Prozessor-Architekturen

Linked Lists The Role of Locking

parallele Prozesse auf sequenziellen Prozessoren Ein Process ist ein typisches Programm, mit eigenem Addressraum im Speicher.

Einführung in die Programmiersprache C

Der neue C++11 Standard Allgemeine Neuerungen und Nebenläufigkeit

Computergrundkenntnisse und Programmieren, WS 07/08, Übung 11: Klassen der Standardbibliothek 2

Memory Models Frederik Zipp

Applications will increasingly need to be concurrent if they want to fully exploit continuing exponential CPU throughput gains.

C++ für alle. Praktische Neuerungen in C September 2012

Grundlagen der Informatik 11. Zeiger

race conditions deadlocks Modernes C++ Sharing data between threads Adrian Ziessler 5. Juni 2012 Adrian Ziessler Modernes C++ 5.

Repetitorium Programmieren I + II

Informatik. Pointer (Dynamisch) Vorlesung. 17. Dezember 2018 SoSe 2018 FB Ing - SB Umwelttechnik und Dienstleistung - Informatik Thomas Hoch 1

5. Behälter und Iteratoren. Programmieren in C++ Überblick. 5.1 Einleitung. Programmieren in C++ Überblick: 5. Behälter und Iteratoren

Threads Einführung. Zustände von Threads

Programmieren in C++ Überblick

Informatik - Übungsstunde

Assoziative Container in C++ Christian Poulter

Praxisorientierte Einführung in C++ Lektion: "Dynamische Speicherverwaltung"

Programmier-Befehle - Woche 08

Advanced Programming in C

Programmieren 2 C++ Überblick

Datenkapselung: public / private

Informatik 1 ( ) D-MAVT F2010. Rekursion, Signaturen. Yves Brise Übungsstunde 8

Wo und wie lange leben Objekte? globale Objekte lokale Objekte dynamische Objekte

Einführung in die Programmierung

Data Structures. Christian Schumacher, Info1 D-MAVT Linked Lists Queues Stacks Exercise

Einführung in die Programmierung mit C++

C++ Teil 6. Sven Groß. 27. Mai Sven Groß (IGPM, RWTH Aachen) C++ Teil Mai / 14

Einleitung Grundlagen Erweiterte Grundlagen Zusammenfassung Literatur. C: Funktionen. Philip Gawehn

Programmier-Befehle - Woche 10

Prüfung A Informatik D-MATH/D-PHYS :15 14:55

Ausnahmebehandlung in Java

Dynamische Datentypen. Destruktor, Copy-Konstruktor, Zuweisungsoperator, Dynamischer Datentyp, Vektoren

Praxisorientierte Einführung in C++ Lektion: "Static Members"

Einführung in die Programmierung Wintersemester 2008/09

4 Generische Programmierung. 4.1 Klassen-Templates (*) 4.2 Funktions-Templates (*) 4.3 Besondere Programmiertechniken (Smart Pointer)

6 Speicherorganisation

Variablen. Deklaration: «Datentyp» «Variablenname» Datentyp bestimmt Größe in Bytes: sizeof Beispiel: long int v; Größe: 4 Bytes

13. Dynamische Datenstrukturen

Programmieren in C. Rekursive Strukturen. Prof. Dr. Nikolaus Wulff

Variablen in C++ Einfache Variablen Strukturen Arrays und Zeichenketten Zeiger und Referenzen Parameter Dynamische Speicherverwaltung

15. Rekursion. Rekursive Funktionen, Korrektheit, Terminierung, Aufrufstapel, Bau eines Taschenrechners, BNF, Parsen

Variablen und Parameter

Programmierung zur Compilezeit. Rainer Grimm

Entwickeln Sie ein C/C++-Programm für die Verwaltung einer Artikeldatei gemäß folgender Vorgaben!

Vorkurs Informatik: Erste Schritte der Programmierung mit C++

Funktionale Programmierung mit C++

Threads. Netzwerk - Programmierung. Alexander Sczyrba Jan Krüger

Annehmende Schleife do while

Probeklausur. Musterlösung

Java Concurrency Utilities

Transkript:

Best Practices Rainer Grimm Training, Coaching und Technologieberatung www.modernescpp.de

Best Practices Allgemein Multithreading Parallelität Speichermodell

Best Practices Allgemein Multithreading Speichermodell

Code Reviews

Code Reviews

Code Reviews

Minimiere Teilen Summation eines Vektors mit 100 Millionen Elementen

Minimiere Teilen Single-Threaded in zwei Variationen

Minimiere Teilen Vier Threads mit einer geteilten Summationsvariable

Minimiere Teilen Vier Threads mit einer einer geteilten, atomaren Summationsvariable

Minimiere Teilen Vier Threads mit einer lokalen Summationsvariable

Minimiere Teilen Die Ergebnisse: Single- Threaded 4 Threads mit Lock 4 Threads mit atomarer Variable 4 Threads mit lokaler Variable 0.07 sec 3.34 sec 1.34 sec 0.03 sec Alles gut?

Die CPU-Auslastung Minimiere Teilen

Minimiere Teilen Die Memory-Wall Der Speicherzugriff bremst die Applikation aus.

Minimiere Warten (Amdahl s Law) 1 1 p p: Parallele Code p = 0.5 2

Mininimiere Warten (Amdahl s Law) By Daniels220 at English Wikipedia, CC BY-SA 3.0, https://commons.wikimedia.org/w/index.php?curid=6678551

Verwende Codeanalyse Werzeuge (ThreadSanitizer) Findet Data Races Speicher- und Performanzoverhead: 10x #include <thread> int main(){ int globalvar{; std::thread t1([&globalvar]{ ++globalvar; ); std::thread t2([&globalvar]{ ++globalvar; ); t1.join(), t2.join();

Verwende Codeanalyse Werzeuge (ThreadSanitizer) g++ datarace.cpp -fsanitize=thread -pthread -g o datarace

Verwende Codeanalyse Werzeuge (ThreadSanitizer)

Verwende Codeanalyse Werzeuge (ThreadSanitizer)

Verwende Codeanalyse Werkzeuge (CppMem)

Verwende Codeanalyse Werkzeuge (CppMem) int x = 0, std::atomic<int> y{0; void writing(){ x = 2000; y.store(11, std::memory_order_release); void reading(){ std::cout << y.load(std::memory_order_acquire) << " "; std::cout << x << std::endl;... std::thread thread1(writing); std::thread thread2(reading); thread1.join(), thread2.join();

Verwende Codeanalyse Werkzeuge (CppMem) int x = 0, atomic_int y = 0; {{{ { x = 2000; y.store(11, memory_order_release); { y.load(memory_order_acquire); x;

Geteilt? Verwende unveränderliche Daten Data Race: Mindestens zwei Threads greifen zu einem Zeitpunkt auf gemeinsame Daten zu. Zumindestens ein Thread versucht diese zu verändern. Veränderlich? nein ja nein OK Ok ja OK Data Race

Verwende unveränderliche Daten Das verbleibende Problem: Die Daten müssen thread-sicher initialisiert werden. 1. Frühzeitiges Initialisieren const int val = 2011; thread t1([&val]{... ; thread t2([&val]{... ; 3. call_once und once_flag void onlyoncefunc{... ; call_once(onceflag, onlyoncefunc); thread t3{ onlyoncefunc() ; thread t4{ onlyoncefunc() ; 2. Konstante Ausdrücke constexpr auto doub = 5.1; 4. Statische Variablen mit Blockgültigkeit void func(){... static int val 2011;... thread t5{ func() ; thread t6{ func() ;

Verwende reine Funktionen Reine Funktionen Erzeugen immer dasselbe Ergebnis, wenn sie die gleichen Argumente erhalten. Besitzen keine Seiteneffekte. Verändern nie den globalen Zustand des Programms. Vorteile Korrektheitsbeweise sind einfacher durchzuführen. Refaktoring und Tests sind einfacher möglich. Ergebnisse von Funktionsaufrufe können gespeichert werden. Die Ausführungsreihenfolge der Funktion kann (automatisch) umgeordnet oder parallelisiert werden.

Verwende reine Funktionen Funktionen int powfunc(int m, int n){ if (n == 0) return 1; return m * powfunc(m, n-1); Metafunktionen template<int m, int n> struct PowMeta{ static int const value = m * PowMeta<m, n-1>::value; ; template<int m> struct PowMeta<m, 0>{ static int const value = 1; ;

Verwende reine Funktionen constexpr Funktionen sind fast reine Funktionen constexpr int powconst(int m, int n){ int r = 1; for(int k = 1; k <= n; ++k) r *= m; return r; auto res = powconst(2, 10); auto constexpr res2 = powconst(2, 10);

Best Practices Allgemein Multithreading Speichermodell

Tasks statt Threads Thread int res; thread t([&]{ res = 3 + 4; ); t.join(); cout << res << endl; Task auto fut = async([]{ return 3 + 4; ); cout << fut.get() << endl; Kriterium Thread Task Beteiligten Erzeuger- und Kinderthread Promise und Future Kommunikation gemeinsame Variable Kommunikationskanal Threaderzeugung verbindlich optional Synchronisation join-aufruf wartet get-aufruf blockiert Ausnahme im Kinderthread Kinder- und Erzeugerthread enden Rückgabewert des get -Aufrufs Formen der Kommunikation Werte Werte, Benachrichtigungen und Ausnahmen

Tasks statt Threads C++20: Erweiterte Future werden die Komposition unterstützen then: Führe den Future aus, sobald der vorherige Future fertig ist. when_any: Führe den Future aus, sobald einer der Futures fertig ist. when_all: Führe den Future aus, sobald alle Futures fertig sind.

Tasks statt Bedingungsvariablen Thread 1 Thread 2 { lock_guard<mutex> lck(mut); ready = true; condvar.notify_one(); { unique_lock<mutex>lck(mut); condvar.wait(lck, []{ return ready; ); prom.set_value(); fut.wait(); Kriterium Bedingungsvariablen Tasks Kritischer Bereich Ja Nein Spurious Wakeup Ja Nein Lost Wakeup Ja Nein Mehrmalige Synchronisation möglich Ja Nein

Mutexe in Locks verpacken Keine Freigabe des Mutex mutex lock_guard mutex m; { m.lock(); shrvar = getvar(); m.unlock(); mutex m; { lock_guard<mutex> mylock(m); shavar = getvar();

Mutexe in Locks verpacken Locken der Mutexe in verschiedener Reihenfolge

Mutexe in Locks verpacken Atomares Locken der Mutexe unique_lock { unique_lock<mutex> guard1(mut1, defer_lock); unique_lock<mutex> guard1(mut2, defer_lock); lock(guard1, guard2); lock_guard (C++17) { std::scoped_lock(mut1, mut2);

Best Practices Allgemein Multithreading Speichermodell

Programmiere nicht lock-frei Fedor Pikus en.cppreference.com

Programmiere nicht lock-frei: ABA A B A

Programmiere nicht lock-frei: ABA Eine lock-freie, einfach verkettete Liste (Stack) TOP A B C Thread 1 möchte A entfernen speichert head = A next = B Thread 2 entfernt A TOP B C entfernt B und löscht B TOP C prüft, ob A == head macht B zum neuen head B wurde bereits durch Thread 2 gelöscht schiebt A auf Liste zurück TOP A C

Programmiere nicht lock-frei: ABA Problem der einfache verketteten Liste B wird gelöscht, obwohl es noch verwendet wird Lösungen: Tagged State: Jeder Knoten bekommt einen Tag, der anzeigt, wie oft er verändert wurde Verzögertes Löschen: Garbage Collection Hazard Pointers: Garbage Collection auf Thread-Ebene RCU: Modifikation werden nur auf Kopien vollzogen Wenn es keinen Zugriff auf die Daten mehr gibt, werden diese ausgetauscht

Verwende bewährte Muster Warten mit Sequenzieller Konsistenz std::vector<int> mysharedwork; std::atomic<bool> dataready(false); void waitingforwork(){ while (!dataready.load() ){ std::this_thread::sleep_for(5ms); mysharedwork[1] = 2; void setdataready(){ mysharedwork = {1, 0, 3; dataready.store(true); int main(){ std::thread t1(waitingforwork); std::thread t2(setdataready); t1.join(); t2.join(); for (auto v: mysharedwork){ std::cout << v << " "; // 1 2 3 ;

Verwende bewährte Muster Warten mit Acquire-Release Semantik std::vector<int> mysharedwork; std::atomic<bool> dataready(false); void waitingforwork(){ while (!dataready.load(std::memory_order_acquire) ){ std::this_thread::sleep_for(5ms); mysharedwork[1] = 2; void setdataready(){ mysharedwork = {1, 0, 3; dataready.store(true, std::memory_order_release); int main(){ std::thread t1(waitingforwork); std::thread t2(setdataready); t1.join(); t2.join(); for (auto v: mysharedwork){ std::cout << v << " "; // 1 2 3 ;

Verwende bewährte Muster Atomare Zähler int main(){ std::vector<std::thread> v; #include <vector> for (int n = 0; n < 10; ++n){ #include <iostream> v.emplace_back(add); #include <thread> #include<atomic> for (auto& t : v) { t.join(); std::cout << count; // 10000 std::atomic<int> count{0; void add(){ for (int n = 0; n < 1000; ++n){ count.fetch_add(1, std::memory_order_relaxed);

Erfinde das Rad nicht neu Threading Building Blocks (TBB) Boost.Lockfree

Erfinde das Rad nicht neu Threading Building Blocks (TBB) Queues concurrent_queue concurrent_bounded_queue concurrent_priority_queue Vektor concurrent_vector Assoziative Container concurrent_unordered_map, concurrent_unordered_multimap concurrent_unordered_set, concurrent_unordered_multiset concurrent_hash_map

Erfinde das Rad nicht neu Boost.Lockfree Queue eine lock-freie, mehrere Erzeuger-Verbraucher (producer-consumer) Queue Stack eine lock-freie, mehrere Erzeuger-Verbraucher (producer-consumer) Stack spsc_queue eine wait-freie, ein Erzeuger-Verbraucher (producer-consumer) Queue (Ringpuffer)

Blogs www.grimm-jaud.de [De] www.modernescpp.com [En] Rainer Grimm Training, Coaching und Technologieberatung www.modernescpp.de