En termes généraux, les algorithmes qui s'exécutent plus rapidement sur le GPU sont ceux où vous effectuez le même type d'instruction sur de nombreux points de données différents.
Un exemple simple pour illustrer cela est la multiplication matricielle.
Supposons que nous faisons le calcul matriciel
A × B = C
Un algorithme CPU simple pourrait ressembler à quelque chose
// commençant par 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];
}
}
}
}
L'essentiel à voir ici est qu'il y a beaucoup de boucles imbriquées pour et chaque étape doit être exécutée l'une après l'autre.
Voir un schéma de ceci
Notez que le calcul de chaque élément de C ne dépend d'aucun des autres éléments. Peu importe donc l'ordre dans lequel les calculs sont effectués.
Ainsi, sur le GPU, ces opérations peuvent être effectuées simultanément.
Un noyau GPU pour calculer une multiplication matricielle ressemblerait à quelque chose
__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];
}
}
}
Ce noyau n'a que les deux boucles for internes. Un programme envoyant ce travail au GPU indiquera au GPU d'exécuter ce noyau pour chaque point de données en C. Le GPU exécutera chacune de ces instructions simultanément sur de nombreux threads. Tout comme le vieil adage "GPU moins cher par la douzaine", les GPU sont conçus pour être plus rapides à faire la même chose de nombreuses fois.
Il existe cependant certains algorithmes qui ralentiront le GPU. Certains ne sont pas bien adaptés au GPU.
Si par exemple, il y avait des dépendances de données, c'est-à-dire: imaginez que le calcul de chaque élément de C dépendait des éléments précédents. Le programmeur devrait mettre une barrière dans le noyau pour attendre la fin de chaque calcul précédent. Ce serait un ralentissement majeur.
En outre, les algorithmes qui ont beaucoup de logique de branchement, à savoir:
__kernel Foo()
{
if (somecondition)
{
do something
}
else
{
do something completely different
}
}
ont tendance à fonctionner plus lentement sur le GPU car le GPU ne fait plus la même chose dans chaque thread.
Il s'agit d'une explication simplifiée car il existe de nombreux autres facteurs à considérer. Par exemple, l'envoi de données entre le CPU et le GPU prend également beaucoup de temps. Parfois, cela vaut la peine de faire un calcul sur le GPU, même quand c'est plus rapide sur le CPU, juste pour éviter le temps d'envoi supplémentaire (Et vice versa).
De nombreux processeurs modernes prennent également en charge la concurrence simultanée avec les processeurs multicœurs hyperthreadés.
Les GPU semblent également ne pas être aussi bons pour la récursivité, voir ici qui explique probablement certains des problèmes avec l'algorithme QR. Je crois que l'on a des dépendances de données récursives.