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
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?