Allgemein ausgedrückt sind Algorithmen, die auf der GPU schneller ausgeführt werden, solche, bei denen Sie denselben Befehlstyp für viele verschiedene Datenpunkte ausführen.
Ein einfaches Beispiel, um dies zu veranschaulichen, ist die Matrixmultiplikation.
Angenommen, wir führen die Matrixberechnung durch
A × B = C
Ein einfacher CPU-Algorithmus könnte ungefähr so aussehen
// beginnend mit C = 0
for (int i = 0; i < C_Width; i++)
{
for (int j = 0; j < C_Height; j++)
{
for (int k = 0; k < A_Width; k++)
{
for (int l = 0; l < B_Height; l++)
{
C[j, i] += A[j, k] * B[l, i];
}
}
}
}
Das Wichtigste dabei ist, dass es viele verschachtelte for-Schleifen gibt und jeder Schritt nach dem anderen ausgeführt werden muss.
Siehe ein Diagramm dazu
Beachten Sie, dass die Berechnung jedes Elements von C von keinem der anderen Elemente abhängt. Es spielt also keine Rolle, in welcher Reihenfolge die Berechnungen durchgeführt werden.
Auf der GPU können diese Vorgänge also gleichzeitig ausgeführt werden.
Ein GPU-Kernel zum Berechnen einer Matrixmultiplikation würde ungefähr so aussehen
__kernel void Multiply
(
__global float * A,
__global float * B,
__global float * C
)
{
const int x = get_global_id(0);
const int y = get_global_id(1);
for (int k = 0; k < A_Width; k++)
{
for (int l = 0; l < B_Height; l++)
{
C[x, y] += A[x, k] * B[l, y];
}
}
}
Dieser Kernel hat nur die beiden inneren for-Schleifen. Ein Programm, das diesen Job an die GPU sendet, weist die GPU an, diesen Kernel für jeden Datenpunkt in C auszuführen. Die GPU führt diese Anweisungen für viele Threads gleichzeitig aus. Genau wie das alte Sprichwort "Billiger im Dutzend", sind GPUs so konzipiert, dass sie oft schneller das Gleiche tun.
Es gibt jedoch einige Algorithmen, die die GPU verlangsamen. Einige sind für die GPU nicht gut geeignet.
Wenn zum Beispiel Datenabhängigkeiten bestünden, dh: Stellen Sie sich die Berechnung jedes Elements von C in Abhängigkeit von den vorherigen Elementen vor. Der Programmierer müsste eine Barriere in den Kernel einbauen, um zu warten, bis die vorherige Berechnung abgeschlossen ist. Dies wäre eine erhebliche Verlangsamung.
Auch Algorithmen, die viel Verzweigungslogik haben, dh:
__kernel Foo()
{
if (somecondition)
{
do something
}
else
{
do something completely different
}
}
neigen dazu, langsamer auf der GPU zu laufen, da die GPU nicht mehr in jedem Thread dasselbe tut.
Dies ist eine vereinfachte Erklärung, da viele andere Faktoren zu berücksichtigen sind. Das Senden von Daten zwischen der CPU und der GPU ist beispielsweise auch zeitaufwändig. Manchmal lohnt es sich, eine Berechnung auf der GPU durchzuführen, auch wenn diese auf der CPU schneller ist, um die zusätzliche Sendezeit zu vermeiden (und umgekehrt).
Viele moderne CPUs unterstützen jetzt auch Parallelität mit Multicore-Prozessoren mit Hyperthreading.
GPUs scheinen auch nicht so gut für Rekursionen zu sein, siehe hier, was wahrscheinlich einige der Probleme mit dem QR-Algorithmus erklärt. Ich glaube, dass man einige rekursive Datenabhängigkeiten hat.