Buscar este blog

sábado, 21 de abril de 2012

Paralelizar en CPU vs GPU

Hoy vamos a tratar dos cuestiones clave a la hora de abordar una implementación en GPU:

los que estén acostumbrados a implementar algoritmos paralelos en CPU tendrán un acceso algo mas llano a la programación GPGPU, pero hay ciertas diferencias a tener en cuenta: la paralelización en CPU difiere considerablemente de la paralelización sobre GPU. La diferencia fundamental se encuentra en el número de procesadores con el que contamos en una y otra arquitectura, si bien en CPU contamos con pocos procesadores pero muy rápidos, en GPU disponemos de muchos procesadores algo más lentos.

La consecuencia directa de esta diferencia implica adoptar un enfoque diferente, orientado a la unidad mínima de procesamiento independiente, evitando en la medida de lo posible, la dependencia entre estas unidades, esto lo tenemos que reflejar tanto en el diseño de las estructuras de datos, como en las tareas que realicemos sobre estas.

Además en GPU podemos adoptar 2 enfoques diferentes:

A.     Enfoque orientado al bloque

Cuando implementamos un kernel en CUDA, éste puede diseñarse de tal manera que deba existir cierta comunicación entre los hilos que ejecutan las tareas. De alguna manera estos hilos colaboran entre sí y pueden pasarse información entre ellos, estableciendo mecanismos de sincronización para acceder a la memoria local donde residen las variables que son comunes al proceso. En este enfoque existe una limitación, la comunicación o colaboración sólo es posible entre hilos que pertenecen al mismo bloque de ejecución. La ventaja principal es que es posible realizar una comunicación muy rápida mediante la memoria local, ya que su tiempo de latencia es mínimo (en comparación con el acceso a memoria global).

La implementación de este enfoque requiere entonces de dos fases, una primera en la que todos los hilos de cada bloque procesan en colaboración obteniendo un resultado por bloque, y una segunda fase en la que son procesados los resultados de cada bloque. Esta tarea se puede ejecutar en otro kernel de GPU o bien directamente en CPU.

B.     Enfoque orientado al hilo

Este enfoque implica una estructura más plana, en la que para una determinada ejecución existe independencia total entre los elementos de cómputo y la tarea que se aplica. Por ejemplo, una tarea que asigne un número aleatorio a cada elemento de un vector puede ser un caso típico.

Aunque inicialmente puede parecer que sí tienen dependencia dada la naturaleza pseudo-aleatoria de estos números, es posible hacer una descomposición de tareas de forma que se pueda dar la independencia de procesos.

Nuestro algoritmo de reducción de vectores propuesto en post anteriores, está implementado utilizando este enfoque. Así, para desligar la dependencia se han dividido las ejecuciones del kernel en niveles, que son ejecutados secuencialmente pero para cada ejecución todas las operaciones son independientes.

El enfoque orientado al hilo permite de una manera más sencilla alinear las lecturas de memoria con cada uno de los hilos que hacen uso de esa memoria, reduciendo el “lag” que produce la latencia de la memoria sobre todo cuando esa relación es de uno a uno.


En resumen: dependiendo del tipo de problema una estrategia será más beneficiosa que la otra, aunque en muchos casos lo idóneo será una mezcla de todas.






No hay comentarios:

Publicar un comentario