Warum Parallele Programmierung?

In Sprachen wie C wird im allgemeinen sequentiell gearbeitet. Dh. eine CPU führt ein Stück Code aus. Dh. zu einem bestimmten Zeitpunkt führt maximal ein Rechenkern den Code aus. Das Programm kann vom Betriebssystem unterbrochen werden, sodass zu einem Zeitpunkt das Programm überhaupt nicht ausgeführt wird (siehe Scheduling).

Die Kunst der parallelen Programmierung besteht nur darin Unabhängigkeiten im Code zu entdecken und dahingehend auszunutzen, dass unabhängige Codestücke gleichzeitig ausgeführt werden können. Das kann einen deutlichen Performancevorteil mit sich bringen.

Nehmen wir das Beispiel der Vektoraddition (C-Programm):

vector.c
#include <stdio.h>
 
#define LENGTH 10
 
int main ()
{
    int a[LENGTH] = {1,2,3,4,5,6,7,8,9,0};
    int b[LENGTH] = {0,9,8,7,6,5,4,3,2,1};
 
    int c[LENGTH];
 
    for (int i=0; i<LENGTH; i++)
    {
        c[i] = a[i] + b[i];
    }
 
    return 0;
}

Wir erkennen, dass innerhalb der Schleife kein c[i] von der Berechnung eines c[k] abhängt für (i != k). Dh. alle Komponenten des Ergebnisvektor c sind einzeln unabhängig voneinander berechenbar. Nehmen wir an, wir haben 10 Rechenkerne, so könnte ein kompletter Vektor in einem Schritt addiert werden (mit Vektorlänge von 10).

Nebenläufigkeit vs. Parallel

Im Allgemeinen beschriebt Parallel die gleichzeitige Ausführung gleichartiger Befehle auf unterschiedliche Daten. Dh. alle Recheneinheiten bearbeiten zu einem Zeitpunkt unterschiedliche Daten, allerdings mit dem selben Befehl (SIMD ⇒ Single Instruction, Multiple Data).

Nebenläufigkeit trifft keine Aussage über eine Gleichzeitigkeit der Berechnung. Threads bspw. werden quasi unabhängig voneiander ausgeführt, es gibt also keine Zusagen darüber ob Thread 1 oder Thread 2 eine Berechnung zuerst beginnt. Da Threads auf verschiedenen Rechenkernen laufen müssen, können unterschiedliche Befehle ausgeführt werden.

Möglichkeiten der Parallelisierung

Zunächst gibt es die Möglichkeit die CPU-Architektur auszunutzen. Die meisten heutigen Desktop-PCs und Laptops verwenden Intels x86 Architektur, mit der 64-Bit Erweiterung von AMD. Für diese Architektur wurden Erweiterungen entwickelt, die es ermöglichen mehrere Daten mit einem Zug mit dem gleichen Befehl zu verarbeiten (SIMD-Befehle). Zunächst wurde MMX, später SSE und AVX entwickelt, die es ermöglichen zunächst 64 Bit, dann 128 Bit, dann 256 Bit und mit AVX-512 sogar 512 Bit gleichzeitig zu berechnen.

Nebenläufigkeit kann mit pThreads (POSIX-Threads) erzeugt werden. Mit pThreads kann der Programmierer genau festlegen, welche Codestücke in einem Thread ausgeführt werden. Im Betriebssystemsinne unterscheiden sich Prozesse und Threads derart, dass mehrere Threads in einer Prozess-Datenstruktur ausgeführt werden können. Die Threads teilen sich den Heap, allerdings hat jeder Thread seinen eigenen Stack, mit dem er arbeiten kann. Bei Threads ist zu beachten, dass hier Race Conditions auftreten können, die ungewollte Folgen haben können.

Mithilfe von OpenMP soll die Parallelisierung von Schleifen und anderen Codestücken sehr einfach werden. So kann eine for-Schleife mit einer #pragma-Direktive parallelisiert werden, wenn der Compiler OpenMP unterstützt.

OpenCL und CUDA sind Frameworks, mit denen auf Grafikkarten (bei OpenCL auch Beschleunigern und CPUs) gerechnet werden kann. Dabei werden die besonderen Eigenschaften einer Grafikkarte verwendet und die massive Parallelität der GPGPUs ausgenutzt.