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

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

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

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

Operating Systems Principles C11

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

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

Informatik - Übungsstunde

Prozesse und Threads. wissen leben WWU Münster

Datenstrukturen und Algorithmen

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

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

Multi-Threading wie in C++11

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

Programmierung und Angewandte Mathematik

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

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

Algorithmen und Datenstrukturen

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

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

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

Konstruktor/Destruktor

Pthreads. David Klaftenegger. Seminar: Multicore Programmierung Sommersemester

28. Parallel Programming II

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

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

Repetitorium Programmieren I + II

Praxis der Programmierung

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

Datenstrukturen und Algorithmen

Programmierkurs C/C++

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

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

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

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

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

Repetitorium Programmieren I + II

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

Ein kleiner Blick auf die generische Programmierung

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

Programmieren in C++ Überblick

Einführung in die Programmierung Wintersemester 2016/17

Typ : void* aktuelle Parameter Pointer von beliebigem Typ

Assoziative Container in C++ Christian Poulter

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

Einführung in die Programmierung mit C++

Zeiger und dynamischer Speicher

Programmieren 2 C++ Überblick

Homogene Multi-Core-Prozessor-Architekturen

Programmierung zur Compilezeit. Rainer Grimm

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

Einführung in die Programmiersprache C

Linked Lists The Role of Locking

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

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

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

Java Concurrency Utilities

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

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

Einstieg in die Informatik mit Java

Threads Einführung. Zustände von Threads

Informatik - Übungsstunde

Memory Models Frederik Zipp

Informatik I (D-MAVT)

13. Dynamische Datenstrukturen

Datenkapselung: public / private

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

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

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

Grundlagen der Informatik 12. Strukturen

Einführung in die Programmiersprache C

C++ Quellcodes zum Buch Kapitel 5

Probeklausur. Musterlösung

Teil 8: Dynamische Speicherverwaltung. Prof. Dr. Herbert Fischer Fachhochschule Deggendorf Prof. Dr. Manfred Beham Fachhochschule Amberg-Weiden

Grundlagen der Informatik 11. Zeiger

DAP2-Programmierpraktikum Einführung in C++ (Teil 2)

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

Aufwand für Threads 124

Lock-free Datenstrukturen

const Zwei Schlüsselwörter bei der Deklaration von Variablen und Member-Funktionen haben wir noch nicht behandelt: const und static.

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

Algorithmen und Datenstrukturen (für ET/IT) Wiederholung: Ziele der Vorlesung. Wintersemester 2012/13. Dr. Tobias Lasser

16. Dynamische Datenstrukturen

C++11: Quo vadis? science + computing ag IT-Dienstleistungen und Software für anspruchsvolle Rechnernetze Tübingen München Berlin Düsseldorf

ALP II Dynamische Datenmengen Datenabstraktion (Teil 2)

Funktionale Programmierung mit C++

Algorithmen und Datenstrukturen (für ET/IT)

Informatik 1 ( ) D-MAVT F2011. Pointer, Structs. Yves Brise Übungsstunde 6

Einführung in die Objektorientierte Programmierung Vorlesung 18: Lineare Datenstrukturen. Sebastian Küpper

Einführung in die Programmierung Wintersemester 2008/09

Praxis der Programmierung

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

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

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(); { auto temp = getvar(); lock_guard<mutex> mylock(m); shavar = temp;

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 B A A template <typename T> T fetch_mult(std::atomic<t>& shar, T mul){ T old = shar.load(); while (!shar.compare_exchange_strong(old, old * mul)); return old; int main(){ std::atomic<int> myint{5; std::cout << myint << std::endl; fetch_mult(myint, 5); std::cout << myint << std::endl; ABA ist eine große Herausforderung in lock-freien Datenstrukturen.

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 Boost.Lockfree CDS (Concurrent Data Structures)

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)

Erfinde das Rad nicht neu Concurrent Data Structures (CDS) Enthält viele intrusive und nicht-intrusive Container Stacks (lock-frei) Queues und Priority-Queues (lock-frei) Ordered lists Ordered sets und maps (lock-frei und mit Lock) Unordered sets und maps (lock-frei und mit Lock)

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