Mientras intentaba mejorar el rendimiento de mi clase de detección de colisión, descubrí que ~ 80% del tiempo que pasaba en la gpu, lo dedicaba a las condiciones de si / de lo contrario solo intentaba descubrir los límites de los cubos por los que debería pasar.
Más precisamente:
cada hilo obtiene una ID, por esa ID obtiene su triángulo de la memoria (3 enteros cada uno) y por esos 3 obtiene sus vértices (3 flota cada uno).
Luego transforma los vértices en puntos de cuadrícula entera (actualmente 8x8x8) y los transforma en los límites del triángulo en esa cuadrícula
Para transformar los 3 puntos en límites, encuentra el mínimo / máximo de cada dimensión entre cada uno de los puntos
Como al lenguaje de programación que estoy usando le falta un mínimo intrínseco, lo hice yo mismo, se ve así:
procedure MinMax(a, b, c):
local min, max
if a > b:
max = a
min = b
else:
max = b
min = a
if c > max:
max = c
else:
if c < min:
min = c
return (min, max)
Entonces, en promedio, debería ser 2.5 * 3 * 3 = 22.5 comparaciones que terminan consumiendo mucho más tiempo que las pruebas reales de intersección triángulo-borde (alrededor de 100 * 11-50 instrucciones).
De hecho, descubrí que calcular previamente los cubos necesarios en la CPU (subproceso único, sin vectorización), apilarlos en una vista de gpu junto con la definición del cubo y hacer que el gpu haga ~ 4 lecturas adicionales por hilo fue 6 veces más rápido que intentarlo para descubrir los límites en el acto. (tenga en cuenta que se vuelven a calcular antes de cada ejecución ya que estoy tratando con mallas dinámicas)
Entonces, ¿por qué la comparación es tan terriblemente lenta en una gpu?
fuente
Respuestas:
Las GPU son arquitecturas SIMD. En las arquitecturas SIMD, cada instrucción debe ejecutarse para cada elemento que procese. (Hay una excepción a esta regla, pero rara vez ayuda).
Por lo tanto, en su
MinMax
rutina no solo cada llamada necesita obtener las tres instrucciones de bifurcación (incluso si en promedio solo se evalúan 2.5), sino que cada declaración de asignación también toma un ciclo (incluso si en realidad no se "ejecuta" )Este problema a veces se llama divergencia de hilo . Si su máquina tiene algo así como 32 carriles de ejecución SIMD, todavía tendrá una sola unidad de recuperación. (Aquí el término "hilo" básicamente significa "carril de ejecución SIMD".) De modo que internamente cada carril de ejecución SIMD tiene un bit "Estoy habilitado / deshabilitado", y las ramas en realidad solo manipulan ese bit. (La excepción es que en el punto donde cada carril SIMD se deshabilita, la unidad de búsqueda generalmente saltará directamente a la cláusula "else").
Entonces, en su código, cada línea de ejecución SIMD está haciendo:
Puede darse el caso de que en algunas GPU esta conversión de condicionales a predicción sea más lenta si la GPU lo hace por sí misma. Como señaló @ PaulA.Clayton, si su lenguaje y arquitectura de programación tienen una operación de movimiento condicional predicada (especialmente una de las formas
if (c) x = y else x = z
), podría hacerlo mejor. (Pero probablemente no mucho mejor).Además, no es necesario colocar el
c < min
condicional dentroelse
dec > max
. Ciertamente no le está ahorrando nada, y (dado que la GPU tiene que convertirlo automáticamente a una predicción) en realidad puede ser doloroso tenerlo anidado en dos condicionales diferentes.fuente