En términos generales, los algoritmos que se ejecutan en la GPU más rápido son aquellos en los que está haciendo el mismo tipo de instrucción sobre muchos puntos de datos diferentes.
Un ejemplo sencillo para ilustrar esto es con la multiplicación de matrices.
Supongamos que estamos haciendo el cálculo de la matriz
A × B = C
Un algoritmo sencillo CPU podría ser algo como
// empezando con 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];
}
}
}
}
La clave para ver aquí es que hay una gran cantidad de bucles for anidados y cada paso deben ser ejecutadas una después de la otra.
Ver un diagrama de esta
Observe que el cálculo de cada elemento de C no depende de ninguno de los otros elementos. Por lo tanto, no importa en qué orden se realicen los cálculos.
Entonces, en la GPU, estas operaciones se pueden realizar simultáneamente.
Un núcleo de GPU para calcular una multiplicación de matriz se vería algo así
__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];
}
}
}
Este núcleo solo tiene los dos bucles for internos. Un programa de envío de este trabajo a la GPU le dirá la GPU para ejecutar este núcleo para cada punto de datos en C. La GPU va a hacer cada una de estas instrucciones al mismo tiempo en muchos hilos. Al igual que el viejo dicho "Más barato por docena" GPU están diseñados para ser más rápido haciendo lo mismo muchas veces.
Sin embargo, hay algunos algoritmos que se ralentizará la GPU abajo. Algunos no son adecuados para la GPU.
Si, por ejemplo, había dependencias de datos, es decir: imaginar el cálculo de cada elemento de C dependía de los elementos anteriores. El programador tendría que poner una barrera en el kernel que esperar a que cada cálculo anterior a fin. Esto sería una gran desaceleración.
Además, los algoritmos que tienen una gran cantidad de ramificación es decir, la lógica:
__kernel Foo()
{
if (somecondition)
{
do something
}
else
{
do something completely different
}
}
tienden a funcionar más lentamente en la GPU porque la GPU ya no está haciendo lo mismo en cada subproceso.
Esta es una explicación simplificada ya que hay muchos otros factores a considerar. Por ejemplo, el envío de datos entre la CPU y la GPU también es mucho tiempo. A veces vale la pena hacer un cálculo en la GPU, incluso cuando su más rápido en la CPU, sólo para evitar el tiempo extra de envío (y viceversa).
También muchos concurrencia de soporte CPU moderna ahora también con los procesadores multinúcleo hyperthreaded.
GPU también parece ser no tan bueno para la recursividad, ver aquí lo que probablemente explica algunos de los problemas con el algoritmo QR. Creo que uno tiene algunas dependencias de datos recursivas.