Skip to content

Latest commit

 

History

History
119 lines (84 loc) · 3.48 KB

File metadata and controls

119 lines (84 loc) · 3.48 KB

Realisierung einer parallel_for Wiederholungsschleife

Zurück


Verwendete Werkzeuge

Klassen:

  • Klasse std::thread
  • Klasse std::mutex
  • Klasse std::lock_guard
  • Klasse std::function

Funktionen:

  • Funktion std::thread::hardware_concurrency

Utility-Funktion:

  • Funktion std::mem_fn

Allgemeines

Wir zeigen in diesem Abschnitt, wie man eine for-Wiederholungsschleife parallelisieren kann. In vielen Bibliotheken gibt es eine solche Funktion unter dem Namen parallel_for. In der STL gibt es eine derartige Funktion nicht, aber es bereitet keine große Mühe, sie selbst zu schreiben.

Eine einfache Realisierung verwendet die so genannten Execution Policies, mit der man ab C++17 viele der STL-Algorithmen parallelisieren kann. In unserem Fall bietet sich der Algorithmus std::for_each an.

Das einzige Problem in der Realisierung besteht darin, wie wir den Schleifenindex nachahmen. Wenn wir std::for_each verwenden wollen, dann iterieren wir über einen Bereich – oder eine View bei Verwendung der Ranges-Bibliothek –, sprich wir haben keine Index-Variable zur Verfügung.

Des Rätsels Lösung besteht in einer Kombination des std::for_each-Algorithmus mit entweder std::iota (STL) oder mit std::views::iota (Ranges). Wir befüllen vor der Ausführung des std::for_each-Algorithmus einen Bereich (Range) mit den gewünschten Indizes und können auf diesem Bereich den std::for_each-Algorithmus ausführen. Der eigentliche Rumpf der for-Wiederholungsschleife kann durch ein Callable oder ein Lambda-Objekt bereitgestellt werden.

Wenn wir für den Index verschiedene ganzzahlige Typen zulassen wollen (z. B. int, std::size_t), dann müssen wir im Funktionstemplate einen entsprechenden Template-Parameter definieren:

01: template <typename TIndex, typename TFunc>
02:     requires std::integral<TIndex>
03: void parallel_for(TIndex first, TIndex last, TFunc func) {
04: 
05:     std::vector<std::size_t> indices(last - first);
06:     std::iota(indices.begin(), indices.end(), first);
07: 
08:     std::for_each(
09:         std::execution::par,
10:         indices.begin(),
11:         indices.end(),
12:         std::move(func)
13:     );
14: }

Oder mit der STL-Ranges-Bibliothek:

01: template <typename TIndex, typename TFunc>
02:     requires std::integral<TIndex>
03: void parallel_for_ranges(TIndex first, TIndex last, TFunc func) {
04: 
05:     auto range{ std::views::iota(first, last) };
06: 
07:     std::for_each(
08:         std::execution::par,
09:         range.begin(),
10:         range.end(),
11:         std::move(func)
12:     );
13: }

An der Ausführung des Programms kann man gut erkennen, dass die parallel_for-Funktion auf 22 Threads aufgeteilt wurde:

[1]:    Start: 1000000000000000001 => 1000000000000005001
[1]:    Elapsed time: 20302 [milliseconds]
[1]:    Threads Used: 22
[1]:    Found Primes: 114

Quellcode

ParallelFor01.h.
PrimeNumbers01.cpp.
PrimeNumbers.cpp.


Literaturhinweise

Die Anregungen zu dieser Funktion stammen im Wesentlichen aus dem Buch „C++ High Performance” von Björn Andrist und Viktor Sehr.

Eine ältere Realisierung einer parallel_for-Funktion stammt im Wesentlichen aus einer Stackoverflow-Frage:

Parallel Loops in C++.


Zurück