c++ arrays cuda

c++ - CUDA, uso de matrices 2D y 3D



arrays (1)

Como su pregunta compila una lista de otras preguntas, responderé compilando una lista de otras respuestas.

cudaMallocPitch / cudaMemcpy2D:

Primero, las funciones de la API de tiempo de ejecución cuda, como cudaMallocPitch y cudaMemcpy2D , en realidad no implican asignaciones de doble puntero ni matrices 2D (doblemente suscritas). Esto es fácil de confirmar simplemente mirando la documentación y observando los tipos de parámetros en los prototipos de funciones. Los parámetros src y dst son parámetros de un solo puntero. No podían estar doblemente suscritos o doblemente desreferenciados. Para un ejemplo de uso adicional, here hay una de las muchas preguntas sobre esto. here hay un ejemplo de uso completamente trabajado. here hay otro ejemplo que cubre varios conceptos asociados con el uso de cudaMallocPitch / cudaMemcpy2d . En cambio, la forma correcta de pensar en esto es que trabajan con asignaciones agudas . Además, no puede usar cudaMemcpy2D para transferir datos cuando la asignación subyacente se ha creado utilizando un conjunto de operaciones malloc (o new , o similares) en un bucle. Ese tipo de construcción de asignación de datos de host es particularmente inadecuado para trabajar con los datos en el dispositivo.

Caso 2D general asignado dinámicamente:

Si desea aprender cómo usar una matriz 2D asignada dinámicamente en un núcleo CUDA (lo que significa que puede usar el acceso doblemente cuda , por ejemplo, data[x][y] ), entonces la página de información de la etiqueta cuda contiene la pregunta "canónica" para Esto, está here . La respuesta dada por los talonmies allí incluye la mecánica adecuada, así como las advertencias apropiadas:

  • hay una complejidad adicional no trivial
  • el acceso generalmente será menos eficiente que el acceso 1D, porque el acceso a datos requiere desreferenciar 2 punteros, en lugar de 1.

(tenga en cuenta que asignar una matriz de objetos, donde los objetos tienen un puntero incrustado a una asignación dinámica, es esencialmente el mismo que el concepto de matriz 2D, y el ejemplo que vinculó en su pregunta es una demostración razonable de eso)

aplastamiento:

Si cree que debe usar el método 2D general, continúe, no es imposible (¡aunque a veces las personas luchan con el proceso!) Sin embargo, debido a la complejidad añadida y la eficiencia reducida, el "consejo" canónico aquí es "aplanar" su método de almacenamiento y utilice el acceso 2D "simulado". Here hay uno de los muchos ejemplos de preguntas / respuestas sobre "aplanamiento".

Caso 3D general asignado dinámicamente:

A medida que ampliamos esto a 3 (¡o más!) Dimensiones, el caso general se vuelve demasiado complejo de manejar, IMO. La complejidad adicional debería motivarnos fuertemente a buscar alternativas. El caso general con subíndice triple implica tres accesos de puntero antes de que los datos se recuperen realmente, por lo que es aún menos eficiente. Here hay un ejemplo completamente trabajado (segundo ejemplo de código).

caso especial: ancho de matriz conocido en tiempo de compilación:

Tenga en cuenta que debe considerarse un caso especial cuando las dimensiones de la matriz (el ancho , en el caso de una matriz 2D, o 2 de las 3 dimensiones para una matriz 3D) se conocen en tiempo de compilación. En este caso, con una definición de tipo auxiliar adecuada, podemos "instruir" al compilador cómo se debe calcular la indexación, y en este caso podemos usar el acceso doblemente suscrito con una complejidad considerablemente menor que el caso general, y no hay pérdida de eficiencia debido a la persecución del puntero. Solo se necesita desreferenciar un puntero para recuperar los datos (independientemente de la dimensionalidad de la matriz, si se conocen n-1 dimensiones en tiempo de compilación para una matriz n-dimensional). El primer ejemplo de código en la respuesta ya mencionada Here (primer ejemplo de código) da un ejemplo completamente trabajado de eso en el caso 3D, y la respuesta here da un ejemplo 2D de este caso especial.

código de host con doble suscripción, código de dispositivo con suscripción simple:

Finalmente, otra opción de metodología nos permite mezclar fácilmente el acceso 2D (doblemente suscrito) en el código del host mientras usamos solo 1D (solo suscrito, quizás con acceso "simulado 2D") en el código del dispositivo . Un ejemplo trabajado de eso está here . Al organizar la asignación subyacente como una asignación contigua, luego construir el "árbol" de punteros, podemos habilitar el acceso doblemente suscripto en el host, y aún así pasar fácilmente la asignación plana al dispositivo. Aunque el ejemplo no lo muestra, sería posible extender este método para crear un sistema de acceso doblemente suscrito en el dispositivo basado en una asignación plana y un "árbol" de puntero creado manualmente, sin embargo, esto tendría aproximadamente los mismos problemas como el método 2D general asignado dinámicamente dado anteriormente: implicaría acceso de doble puntero (doble desreferencia), por lo que es menos eficiente, y hay cierta complejidad asociada con la construcción del "árbol" de puntero, para usar en el código del dispositivo (por ejemplo, sería necesita una operación adicional de cudaMemcpy , probablemente).

De los métodos anteriores, deberá elegir uno que se adapte a su apetito y necesidades. No hay una sola recomendación que se ajuste a todos los casos posibles.

Hay muchas preguntas en línea sobre la asignación, copia, indexación, etc. de matrices 2d y 3d en CUDA. Recibo muchas respuestas contradictorias, así que intento compilar preguntas pasadas para ver si puedo hacer las correctas.

Primer enlace: https://devtalk.nvidia.com/default/topic/392370/how-to-cudamalloc-two-dimensional-array-/

Problema: asignación de una matriz 2D de punteros

Solución de usuario: use mallocPitch

Solución ineficiente "correcta": use malloc y memcpy en un bucle for para cada fila (sobrecarga absurda)

Solución "más correcta": aplástalo en una "opinión profesional" de matriz 1d, un comentario que dice que nadie que tenga en cuenta el rendimiento utiliza estructuras de puntero 2D en la GPU

Segundo enlace: https://devtalk.nvidia.com/default/topic/413905/passing-a-multidimensional-array-to-kernel-how-to-allocate-space-in-host-and-pass-to-device-/

Problema: asignar espacio en el host y pasarlo al dispositivo

Subenlace: https://devtalk.nvidia.com/default/topic/398305/cuda-programming-and-performance/dynamically-allocate-array-of-structs/

Solución de subenlace: la codificación de estructuras basadas en punteros en la GPU es una mala experiencia y altamente ineficiente, aplástala en una matriz 1d.

Tercer enlace: Asignar matriz 2D en la memoria del dispositivo en CUDA

Problema: asignación y transferencia de matrices 2d

Solución de usuario: use mallocPitch

Otra solución: aplanarlo

Cuarto enlace: ¿Cómo usar matrices 2D en CUDA?

Problema: asignar y recorrer matrices 2d

Solución enviada: no muestra la asignación

Otra solución: aplastarlo

Hay muchas otras fuentes que en su mayoría dicen lo mismo, pero en varios casos veo advertencias sobre estructuras de puntero en la GPU.

Muchas personas afirman que la forma correcta de asignar una matriz de punteros es con una llamada a malloc y memcpy para cada fila, aunque existen las funciones mallocPitch y memcpy2D. ¿Son estas funciones de alguna manera menos eficientes? ¿Por qué no sería esta la respuesta predeterminada?

La otra respuesta ''correcta'' para las matrices 2d es aplastarlas en una matriz. ¿Debería acostumbrarme a esto como un hecho de la vida? Soy muy perspicaz con mi código y me parece poco elegante.

Otra solución que estaba considerando era maximizar una clase de matriz que usa una matriz de puntero 1d pero no puedo encontrar una manera de implementar el operador de doble paréntesis.

También de acuerdo con este enlace: ¿ Copiar un objeto al dispositivo?

y la respuesta del subenlace : error de segmentación de cudaMemcpy

Esto se pone un poco dudoso.

Las clases con las que quiero usar CUDA tienen todas matrices 2 / 3d y ¿no habría mucha sobrecarga al convertirlas en matrices 1d para CUDA?

Sé que he preguntado mucho, pero en resumen, ¿debería acostumbrarme a las matrices aplastadas como un hecho real o puedo usar las funciones de asignación y copia 2d sin obtener una sobrecarga mala como en la solución donde se llama a alloc y cpy para ¿lazo?