Parallel .NET

Zusammenfassung der Webcast-Serien PPL und TPL von Bernd Marquardt

Get Started. It's Free
or sign up with your email address
Parallel .NET by Mind Map: Parallel .NET

1. Ausgangslage

1.1. Geschichte

1.1.1. im Schnitt alle 2 Jahre höhere CPU-Leistung

1.1.2. von neuer Hardware profitierte auch die Software

1.1.3. Grenze bei 3-4 Ghz erreicht

1.1.3.1. Kühlung wird schwierig

1.1.3.2. nach Entwicklungskurve müsste die Leistung heute bei ca. 50Ghz liegen

1.2. Wandel

1.2.1. Multi Core

1.2.1.1. Multi Processing (OS)

1.2.1.2. Multi-Threading

1.2.2. Mehrere CPUs auf einem Chip

1.2.3. Anpassungen an den Programmen, wenn man von der Leistung profitieren will

1.3. Parallel Programmierung wird wichtiger

1.3.1. selbst programmieren

1.3.2. Multi-Threading

1.3.3. bedeutet mehrere Prozessoren oder Kerne arbeiten gleichzeitig

2. Einführung

2.1. Amdahl's Gesetz

2.1.1. Programm was parallel auf N Prozessoren läut, ist nicht automatisch N mal so schnell.

2.1.1.1. das gesamte Programm kann nicht parallel laufen

2.1.1.2. ein Teil läuft nach wie vor sequentiell ab

2.1.1.2.1. Textdateien

2.1.1.2.2. Variablen initialisieren

2.1.1.3. Sp = T* / Tp

2.1.1.3.1. Sp Speedup

2.1.1.3.2. T* Optimale seq. Ausführungszeit

2.1.1.3.3. Tp Optimale parallele Ausführungszeit

2.1.1.4. Erhöhung der Anzahl Proz. bringt nicht unbedingt viel, weil die seq. Abhängigkeit es nicht zulässt

2.1.1.5. Abhängigkeit zum OS

2.1.1.5.1. Threaderzeugung kostet Zeit

2.1.1.5.2. Zerstörungszeit der Threads

2.1.1.5.3. Kontext-Switch

2.1.1.5.4. Abgleich Cache-Inhalte

2.1.1.5.5. Verlangsamung des OS Schedulers durch andere Threads

2.1.1.5.6. Granularität (Performance) der parallelen Teileinheiten

2.1.1.5.7. Synchronisierung

2.1.1.5.8. Best Practices

2.1.1.6. zu lösendes Problem lässt paralleles Arbeiten nicht zu

2.1.2. Wenn die Zeit, die ein Thread arbeitet sehr viel länger ist, als die Erzeugungszeit des Threads, dann ist alles in Ordnung

2.1.3. Ausführungszeit nach Amdahl

2.1.3.1. T = T* + Tp / p (Formel ohne Zeit für Threaderzeugung)

2.1.4. Wenn der Thread nur sehr wenig zu tun hat, dann kann die Parallel-Ausführungszeit langsamer werden, als die sequentielle Ausführungszeit

2.2. Dead Locks

2.2.1. entsteht wenn ein Thread ein Objekt in Benutzung hat und nicht mehr frei gibt, welches ein andere Thread benutzen will

2.2.1.1. wichtig:

2.2.1.1.1. Sperr-Reihenfolge einhalten

2.2.1.1.2. Backoff-Strategie

2.2.2. Synchronisierung beinhaltet immer die Gefahr eines "Deadlocks"

2.2.3. Laufzeiterscheinung, es kann den statischen Code nicht unbedingt angesehen werden

2.2.4. Unterschiedliche Rechner, erzeugen unterschiedliches Deadlock-Verhalten

2.2.4.1. Abhängig von Anzahl CPU-Kerne und Prozesse

2.2.5. Deadlocks sind kaum zu debuggen

2.2.5.1. vielleicht mal in der Zukunft

2.2.5.2. Alternative Log4Net oder Enterprise Library

2.3. Data Races

2.3.1. entstehen, wenn mehrere Treads auf gleiche Ressourcen (Variable) schreibend zugreifen

2.3.2. Ergebnis nicht vorhersehbar

2.3.2.1. der schnellere Thread gewinnt, bzw. verliert

2.3.3. sind im Code nicht sofort erkennbar

2.3.3.1. Es kann ein paar mal gut gehen

2.3.4. Es hängt von der Anzahl der parallelen Threads ab

2.3.5. Compiler kann keine Fehlermeldung ausgeben

2.3.6. Nebenläufiger Zugriff auf eine Variable

2.4. Parallelisierungstechniken

2.4.1. Normale Threads

2.4.2. Thread Pools

2.4.3. OpenMP

2.4.4. MPI

2.4.5. PPL

2.4.6. Task Parallel Library (TPL)

3. Parallel Pattern Library (PPL)

3.1. Ausgangslage

3.1.1. Neue Bibliotheken sollen den direkten Zugriff of die OS-Thread vereinfachen

3.1.2. Compiler Support ab VS 2010

3.1.3. Zwei Welten

3.1.3.1. Native C++ Code

3.1.3.1.1. Benutzt PPL

3.1.3.1.2. Mit Compiler Switch /clr nicht erlaubt

3.1.3.2. Managed Code

3.1.3.2.1. VB, C#, F#, C++/CLI

3.1.3.2.2. Benutzung der Task Parallel Library (TPL)

3.2. Vorgehensweise

3.2.1. header

3.2.1.1. #include<ppl.h>

3.2.2. namespace

3.2.2.1. using namespace Concurrency;

3.3. Tasks

3.3.1. sind kleine Ausführungseinheiten

3.4. Task-Gruppen

3.4.1. Mehrere Tasks zusammengefasst

3.4.2. Können bis zu 10 Tasks pro Gruppe ausführen

3.4.3. Ablauf

3.4.3.1. task_group erzeugen

3.4.3.2. mit run() starten

3.4.3.3. Aufgabe im Haupt-Thread starten

3.4.3.4. mit wait() warten (Syncronisierung)

3.4.3.5. Prüfen ob Schleifen paralleisiert werden können

3.5. Agenten

3.5.1. C+ Template Library

3.5.2. In-process message passing

3.5.3. Ideal für fein granulare Datenfluss-Aufgaben

3.5.3.1. Dataflow

3.5.3.2. Piplining

3.5.4. bauen auf Concurrency Runtime auf

3.5.5. Einsatzzweck

3.5.5.1. Viele Operationen die miteinander kommunizieren sollen/müssen

3.5.5.2. Erzeugung von Daten-Pipelines, Daten-Netzwerken

3.5.5.3. Aufgabentrennung durch Komponenten

3.5.5.4. Weitergabe von Ergebnissen

3.5.5.5. Weniger geeignet für Performance-Optimierung

3.5.6. Ablauf

3.5.6.1. Message passing

3.5.6.1.1. send, receive

3.5.6.2. send

3.5.6.2.1. Message landet in einem Buffer

3.5.6.3. receive

3.5.6.3.1. holt die Nachrichten aus dem Buffer

3.6. Synchronisierung

3.6.1. critical_section

3.6.1.1. critical_section object

3.6.1.1.1. lock

3.6.1.1.2. unlock

3.6.1.2. Vorsicht mit worst-case lock

3.6.1.2.1. lock()

3.6.1.2.2. Anweisung

3.6.1.2.3. unlock()

3.6.1.3. lock/unlock ist Zeitaufwendig, InterlockedIncrement verwenden

3.6.1.4. nicht die optimalste Lösung

3.6.1.4.1. Performancetests machen

3.6.2. reader_writer_lock

3.6.2.1. mehrere lesen, einer schreibt

3.6.3. event

3.7. Beispiele

4. Task Parallel Library (TPL)

4.1. Einführung

4.1.1. Klasse

4.1.1.1. System.Threading.dll - Referenz

4.1.1.2. System.Threading.Parallel

4.1.2. Granularität

4.1.2.1. Arbeitseinheit darf nicht zu klein sein

4.1.2.1.1. je mehr Prozessoren, desto grösser die Thread-Erzeugungszeit

4.1.3. setzt auf ThreadPool als Standard-Scheduler auf

4.1.4. Hill-climbing Methoden für die optimale Thread-Anzahl

4.1.5. interne Verwendung von SpinWait und SpinLock zur Synchronisierung

4.1.6. Task stealing

4.1.6.1. Tasks anderen Threads zuordnen

4.2. Schleifen

4.2.1. Methoden

4.2.1.1. Parallel.For

4.2.1.2. Parallel.ForEach

4.2.2. Ablauf

4.2.2.1. Aufteilung in kleine Bröckchen

4.2.2.2. Zuteilung an Thread, wenn dieser die aktuelle Aufgabe komplett erledigt hat

4.2.3. Vorsicht

4.2.3.1. Schleifendurchläufe müssen unabhängig voneinander sein

4.2.3.2. Unausgewogene Schleifen

4.2.3.2.1. Ausführung basierend auf index-Variable mit if, switch etc.

4.2.3.2.2. Algorithmus optimieren

4.2.3.3. mehrere Threads schreiben auf die gleiche Variable (führt zu Rechenfehlern)

4.2.3.3.1. Synchronisieren mit

4.2.3.4. vor kleinen Arbeitseinheiten

4.2.3.5. Disk-IO Parallelisierung

4.2.3.5.1. Abhängig von der Hardware

4.2.3.5.2. lohnt erst ab Solid State Disks

4.2.3.6. User Interface

4.2.3.6.1. Bei Output-Daten

4.2.3.7. Bildbearbeitung

4.2.3.7.1. Einfache Algorithmen nicht parallelisieren

4.2.4. Aggregationen

4.2.4.1. Zusammenfassung von Ergebnissen

4.2.4.2. Meistens Locking erforderlich

4.2.4.3. Ablauf

4.2.4.3.1. Vor der Schleife Laufzeitvariable, ParallelLoopState und ThreadLocalState initialsieren

4.2.4.3.2. Schleife abarbeiten (LocalState benutzen)

4.2.4.3.3. Nach der Schleifenabarbeitung Aggregation der Ergebnisse (Achtung: Locking)

4.2.4.4. Rechenungenauigkeit

4.3. Code-Bereiche parallelisieren

4.3.1. Methode

4.3.1.1. Parallel.Invoke

4.3.1.1.1. aktuell bis 10 Aufrufe möglich

4.3.2. Vorsicht

4.3.2.1. Gleichzeitiger Zugriff auf die gleichen Daten (schreibend)

4.3.2.2. erst Nachdenken

4.3.2.3. Seiteneffekte bedenken

4.4. Task

4.4.1. Task.Factory.StartNew

4.4.1.1. TaskFactory-Klasse

4.4.1.1.1. FromAsync

4.4.1.1.2. ContinueWith

4.4.1.1.3. ContinueWhenAll

4.4.1.1.4. ContinueWhenAny

4.4.2. Abbruch

4.4.2.1. t1.Cancel()

4.4.2.2. Auf Threadsicheren Code achten (siehe Demo10, statische Rückgabevariable)

4.4.3. warten

4.4.3.1. Task.WaitAll(t1, t2)

4.4.3.2. t1.Wait()

4.4.4. Beendigung

4.4.4.1. Prüfbar mit t1.IsCompleted

4.4.4.1.1. Cancel nicht feststellbar

4.4.5. Eigenschaften

4.4.5.1. Synchronisierung

4.4.5.2. Abbrechen von Einheiten

4.4.5.3. Benutzung wie normale Threads

4.5. Task<TResult>

4.5.1. Task<TResult>.Factory.StartNew

4.5.2. Ermöglicht asynchrone Berechnung

4.5.2.1. Abruf with resultVar.Result

4.5.3. lock weiterhin von Bedeutung für Threadsicheren Programmcode

4.5.4. Properties der Result-Variable

4.5.4.1. result.Result

4.5.4.1.1. wenn Ergebnis noch nicht berechnet, wartet Result bis das Ergebnis berechnet ist.

4.5.4.1.2. kann auch zur Bremse werden, wenn die Berechnungen sehr lange laufen

4.5.4.2. result.IsCanceled

4.5.4.3. result.IsCompleted

4.5.4.4. ContinueWith

4.5.4.4.1. Ausführungen nach Berechnungen (result.Result)

4.5.5. Verwendbar im UI

4.5.5.1. WinForms

4.5.5.2. WPF

4.5.5.3. Bei asynchronen Aufrufen werden die EventHandler nicht blockiert

4.5.5.4. Alte Regel = neue Regel

4.5.5.4.1. laufen im STA

4.5.5.4.2. Nur der Thread darf auf Controls zugreifen, der sie erzeugt hat

4.5.5.4.3. Ab .NET 2.0 Exceptions nur im Debug-Modus

4.5.5.5. Synchronisierung mit Controls

4.5.5.5.1. WinForms

4.5.5.5.2. WPF

4.6. TaskScheduler

4.6.1. Abstrakte Basisklasse

4.6.1.1. interessant für Prioritätsverwaltung

4.6.2. Neue WorkStealing-Queues in ThreadPools

4.6.3. Zugriff auf das User Interface

4.6.3.1. TaskSheduler.FromCurrentSyncronizationContext()

4.6.3.1.1. notwendig für ContinueWith

4.6.3.2. Async-Berechnung

4.6.3.3. Fortführung mit ContinueWith (im korrekten UI-Thread)

4.6.3.4. früher

4.7. Fehlerbehandlung

4.7.1. Concurrent Exceptions

4.7.1.1. Thread läuft bis zu einem Safepoint bevor er abgebrochen werden kann

4.7.1.2. paralelle Anwendungen können mehrere Exceptions werfen

4.7.1.3. oft verarbeitet ein anderer Thread diese Exception

4.7.1.4. bei einen Fehler werden alle Threads angehalten, wenn der nächste Safepoint des jeweiligen Threads erreicht wurde

4.7.1.5. Weitere Exceptions während des Abbruchs möglich

4.7.2. komplex im parallelen Code

4.7.3. System.Threading.AggregateException

4.7.3.1. sammelt alle Fehlermeldungen

4.7.3.2. wenn alle Threads stehen, wird diese neu geworfen

4.7.3.2.1. war bisher nicht möglich

4.7.3.2.2. einfacher try/catch block um die parallel-Verarbeitung reicht jetzt aus

4.7.3.3. Abfrage aller Fehler über die InnerExceptions - Auflistung

4.7.3.4. Klassen die diese Exception werfen

4.7.3.4.1. Parallel

4.7.3.4.2. Task

4.7.3.4.3. Task<TResult>

4.7.3.4.4. Parallel LINQ Abfragen (AsParallel)

4.8. Synchronisierung

4.8.1. Barrier

4.8.2. CountDownEvent

4.8.3. LazyInit<T>

4.8.3.1. Variable hat zusätzlich Properties

4.8.3.1.1. IsInitialized

4.8.3.1.2. Mode

4.8.3.1.3. Value

4.8.4. ManualResetEventSlim

4.8.5. SemaphoreSlim

4.8.6. SpinLock

4.8.7. SpinWait

4.8.8. WriteOnce<T>

4.8.8.1. erzwingt die einmalige Initialisierung

4.8.8.2. weitere Initialisierung wirft Exception

4.8.8.3. bisher nur mit readonly im Constructor

4.8.9. Collections

4.8.9.1. ConcurrentQueue

4.8.9.2. ConcurrentStack

4.8.9.2.1. Namespace System.Treading.Collections

4.8.9.2.2. Prüfbar mit TryPop(out val)

4.8.9.3. BlockingColle

4.9. PLINQ

4.9.1. Ausgenommen sind

4.9.1.1. Linq to SQL

4.9.1.2. Linq to Entities

4.9.1.3. wenn PLINQ der Meinung ist, dass der sequenzielle Ausführungsmodus besser ist

4.9.2. Code-Lesbarkeit wird verbessert

4.9.3. Ausführung mit AsParallel()

4.9.3.1. nutzt alle verfügbaren Prozessoren

4.9.3.1.1. Where

4.9.3.1.2. OrderBy

4.9.3.1.3. Select

4.9.4. andere Rückgabetypen

4.9.4.1. aus OrderedEnumerable<T> wird OrderedParallelQuery<T>

4.9.5. Verzögerte Ausführung wie bei LINQ

4.9.6. Verarbeitungsvarianten

4.9.6.1. WithMergeOptions(opt)

4.9.6.1.1. FullyBuffered

4.9.6.1.2. NotBuffered

4.9.6.1.3. AutoBuffered

4.9.7. Abbruch

4.9.7.1. Abbruchmarke setzen

4.9.7.1.1. var cts = new CancellationTokenSource()

4.9.7.1.2. WithCancellation(cts)

4.9.7.2. Abfrage mit

4.9.7.2.1. cts.IsCancellationRequested

4.9.7.3. Ausnahme

4.9.7.3.1. OperationCanceledException

4.9.8. Schwachstellen

4.9.8.1. Bei Schleifen auf Threadsicherheit achten

4.9.8.2. AsOrdered() kann zu Geschwindigkeitseinbussen führen

4.9.8.3. Abbruch einer Abfrage ist trickreich

4.9.8.4. IsCancellationRequested

4.9.8.4.1. zu viel abfragen verringert Performance

4.9.8.4.2. zu wenig, verlangsamt die Beendigung

4.10. Best Practices

4.10.1. zuerst prüfen ob Algorithmus optimiert werden kann, bevor parallelisiert wird.

4.10.2. Verschachtelte Schleifen

4.10.2.1. Nach Möglichkeit die äussere Schleife parallelisieren

4.10.2.1.1. Bessere Performance

4.10.2.2. Parallelisieren der inneren Schleife sollten Sonderfälle bleiben

4.10.3. Bei kleinen und mittleren Arbeitseinheiten

4.10.3.1. ParallelOptions

4.10.3.1.1. Festlegen wie viele Kerne max benutzt werden dürfen

4.10.3.1.2. in früheren Beta-Versionen

4.10.3.1.3. var options = new ParallelOptions { MaxDegreeOfParallelism = 2 }

4.10.3.1.4. MaxDegreeOfParallelism > Prozessoren * Kerne

4.10.4. User Interface

4.10.4.1. Anfallende Output-Daten sammeln

4.10.4.2. Synchronisierung so wenig wie möglich

4.10.4.3. UI-Zugriff mittels Invoke oder BeginInvoke so selten wie möglich

4.11. Beispiele

4.11.1. bis DemoTPL5.zip sind die Beispiele in VS2008

4.11.2. Pattern & Practice Parallel Programming

4.11.3. Building parallelized apps with .NET and VS