Los hilos CUDA no están pensados para cálculos pesados.

Los hilos no están pensados para cálculos pesados, moraleja…

Una semana de trabajo, la moraleja…

Tras jugar una semana con CUDA creo entender que la finalidad de la arquitectura es la resolución de grandes sistemas de ecuaciones (lo que trataré en el siguiente post) ya sean estas lineales o no o en definitiva la aplicación masiva de pequeñas transformaciones sobre un grupo de elementos por lo general bastante grande, si queremos amortizar los costes de transferencia. Supongo que nadie piensa que he descubierto nada, es para lo que está diseñado, lo sé. Sin embargo he llegado a esa conclusión tras jugar un poco, os cuento:
He implementado un algoritmo genético sobre CUDA pero con una perspectiva diferente a la que se plantea normalmente. Para quien no haya escuchado nunca el término “algoritmo genético” que no se preocupe, basta por ahora en fijarnos en sus requerimientos compuacionales . Un algoritmo genético trabaja mediante iteraciones sobre una “población” que no es otra cosa que un número definido de vectores binarios de la misma longitud. Cada uno de estos vectores ui requiere de su evalucación, es decir, se le aplican una serie de transformaciones de forma que obtenemos un valor νi llamado en la jerga como “fitness” o “aptitud” del individuo, es decir:
ui → νi
La transformación que nos lleve a obtener el fitness está determinada por nuestro problema, en mi caso es la resolución de un sistema lineal de ecuaciones de orden 200, lo cual no representa ningún reto cualquier procesador. Bien, como en cada iteración tenía que tratar con 50 individuos tenía que resolver 50 sistemas en cada iteración. Aquí viene el punto de inflexión; en lugar de procesar el vector en la CPU y mandar después un sistema de ecuaciones tras otro al device como dice el estándar, pensé que podía tratar un vector por hilo y que cada hilo resolviese el sistema, ventajas de esto:
  • Todos los vectores se procesarían a la vez.
  • Supresión de los tiempos de copia del host-device, device-host.
  • Poder realizar todas las iteraciones desde el device llamando a un único kernel desde el main().
Pretendía sacar partido de esto para hacer un algoritmo eficiente, sin embargo os dejo una máxima: LOS HILOS NO ESTÁN PENSADOS PARA CÁLCULOS PESADOS. Las conclusiones saltaron a la vista, y las limitaciones son estas:
  • El tamaño de la memoria compartida es escaso para plantear las cosas de esta forma, si jugáis con Teslas es otro tema. Hay que hacer malabares, y más si tratamos con floats. Por lo general, tener poco espacio disponible en la memoria compartida, a pesar de ser una de las mayores limitaciones a la hora de sacar rendimiento de estos dispositivos, no suele ser un impedimiento para manejar matrices de varios millones de elementos porque podemos cargar la matriz segmentada en el device para hacer cálculos muy rápidos.
  • Los cálculos se efectúan de forma más lenta. Esto es debido a varias cosas; primero a que el clock de la gráfica va más lento (en mi caso 1.6 MHz frente a 4.4 del procesador). Segundo, al tener que manejar la memoria de forma “manual” tenemos que realizar esperas de sincronización entre los hilos después de copiar o de cargar algo, lo que ralentiza también. Tercero, básicamente es la misma razón que la anterior, la memoria compartida, nos impide realizar todos los cálculos a la vez como pretendía así que tendremos que hacerlos por oleadas.
  • Debido a los requerimientos de mi función de evaluación perdí bastante precisión. Dependiendo de las aplicaciones ésto puede ser o no una limitación.
  • Si pretendemos llevar un log de los cálculos que realizamos los tiempos se disparan. Para, copia al main, copia al disco, vuelve a iniciar el kernel…
Pese a lo anterior, reduje el tiempo de ejecución del algoritmo (100 iteraciones) a aproximadamente una octava parte del empleado por el procesador. No está mal, pero sin el factor limitante de la memoria compartida el tiempo hubiese sido de un orden menos de magnitud y tendería a reducirse al aumentar el número de iteraciones.
Moraleja, si el tiempo es un factor clave en la ejecución de los cálculos puede merecer la pena lidiar con las complicaciones implícitas de la paralelización del algoritmo, el uso de la memoria compartida, etc… 18 del tiempo inicial es una mejora considerable, sin olvidar que tratamos con una gráfica de clase media y sin una arquitectura optimizada para el cálculo puro… Quiero una de estas. O dos.

Esta entrada fue publicada en Algoritmo genético, CUDA. Guarda el enlace permanente.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s