punteros puntero matrices lenguaje comparar aritmetica apuntadores c cuda malloc

matrices - Uso de cudamalloc(). ¿Por qué el doble puntero?



punteros void lenguaje c (5)

El problema: tiene que devolver dos valores: Código de retorno Y puntero a la memoria (en caso de que el código de retorno indique éxito). Entonces debe hacer que uno de ellos sea un puntero para devolver el tipo. Y como tipo de retorno, tiene la opción entre el puntero de retorno a int (para código de error) o el puntero de retorno a puntero (para dirección de memoria). Allí, una solución es tan buena como la otra (y una de ellas produce el puntero al puntero (prefiero usar este término en lugar de puntero doble , ya que suena más como un puntero a un número doble de punto flotante)).

En Malloc tiene la buena propiedad de que puede tener punteros nulos para indicar un error, por lo que básicamente necesita un solo valor de retorno ... No estoy seguro de si esto es posible con un puntero a la memoria del dispositivo, ya que podría ser que no o un valor nulo incorrecto (recuerde: esto es CUDA y NO Ansi C). Podría ser que el puntero nulo en el sistema host sea completamente diferente del nulo utilizado para el dispositivo, y como tal, la devolución del puntero nulo para indicar errores no funciona, y usted debe hacer la API de esta manera (eso también significaría que no tiene NULL común en ambos dispositivos).

Actualmente estoy repasando los ejemplos del tutorial en http://code.google.com/p/stanford-cs193g-sp2010/ para aprender CUDA. El código que demuestra __global__ funciones se da a continuación. Simplemente crea dos matrices, una en la CPU y otra en la GPU, rellena la matriz GPU con el número 7 y copia los datos de la matriz GPU en la matriz de la CPU.

#include <stdlib.h> #include <stdio.h> __global__ void kernel(int *array) { int index = blockIdx.x * blockDim.x + threadIdx.x; array[index] = 7; } int main(void) { int num_elements = 256; int num_bytes = num_elements * sizeof(int); // pointers to host & device arrays int *device_array = 0; int *host_array = 0; // malloc a host array host_array = (int*)malloc(num_bytes); // cudaMalloc a device array cudaMalloc((void**)&device_array, num_bytes); int block_size = 128; int grid_size = num_elements / block_size; kernel<<<grid_size,block_size>>>(device_array); // download and inspect the result on the host: cudaMemcpy(host_array, device_array, num_bytes, cudaMemcpyDeviceToHost); // print out the result element by element for(int i=0; i < num_elements; ++i) { printf("%d ", host_array[i]); } // deallocate memory free(host_array); cudaFree(device_array); }

Mi pregunta es por qué han redactado el cudaMalloc((void**)&device_array, num_bytes); declaración con un doble puntero? Incluso here definición de cudamalloc () en dice que el primer argumento es un doble puntero.

¿Por qué no simplemente devolver un puntero al comienzo de la memoria asignada en la GPU, tal como lo hace la función malloc en la CPU?


En C / C ++, puede asignar un bloque de memoria dinámicamente en tiempo de ejecución llamando a la función malloc .

int * h_array h_array = malloc(sizeof(int))

La función malloc devuelve la dirección del bloque de memoria asignado que puede almacenarse en una variable de algún tipo de puntero.
La asignación de memoria en CUDA es un poco diferente de dos maneras,

  1. El cudamalloc devuelve un entero como código de error en lugar de un puntero al bloque de memoria.
  2. Además del tamaño de byte a asignar, cudamalloc también requiere un puntero de doble vacío como primer parámetro.

    int * d_array cudamalloc ((void **) & d_array, sizeof (int))

La razón detrás de la primera diferencia es que todas las funciones de API CUDA siguen la convención de devolver un código de error entero. Entonces, para hacer que las cosas sean consistentes, la API cudamalloc también devuelve un número entero.

Hay requisitos para un doble puntero ya que el primer argumento de la función se puede entender en dos pasos.

En primer lugar, dado que ya hemos decidido hacer que cudamalloc devuelva un valor entero, ya no podemos usarlo para devolver la dirección de la memoria asignada. En C, la única otra manera para que una función se comunique es pasando el puntero o la dirección a la función. La función puede realizar cambios en el valor almacenado en la dirección o la dirección a la que apunta el puntero. Los cambios a esos valores se pueden recuperar más tarde fuera del alcance de la función utilizando la misma dirección de memoria.

cómo funciona el doble puntero

El siguiente diagrama ilustra cómo funciona con el doble puntero.

int cudamalloc((void **) &d_array, int type_size) { *d_array = malloc(type_size) return return_code }

¿Por qué necesitamos el doble puntero? Por qué esto funciona

Normalmente vivo en el mundo de las pitones, así que también me costó entender por qué esto no funciona.

int cudamalloc((void *) d_array, int type_size) { d_array = malloc(type_size) ... return error_status }

Entonces, ¿por qué no funciona? Porque en C, cuando se llama cudamalloc, se crea una variable local llamada d_array y se le asigna el valor del primer argumento de la función. No hay forma de que podamos recuperar el valor en esa variable local fuera del alcance de la función. Por eso necesitamos un puntero a un puntero aquí.

int cudamalloc((void *) d_array, int type_size) { *d_array = malloc(type_size) ... return return_code }


Esto es simplemente un horrible y horrible diseño de API. El problema con el paso de dos punteros para una función de asignación que obtiene memoria abstracta ( void * ) es que tiene que hacer una variable temporal de tipo void * para contener el resultado, luego asignarlo al puntero real del tipo correcto que desee usar. La (void**)&device_array , como en (void**)&device_array , no es válida C y da como resultado un comportamiento indefinido. Simplemente debe escribir una función de contenedor que se comporte como malloc normal y devuelva un puntero, como en:

void *fixed_cudaMalloc(size_t len) { void *p; if (cudaMalloc(&p, len) == success_code) return p; return 0; }


Lo convertimos en doble puntero porque es un puntero al puntero. Tiene que apuntar a un puntero de memoria GPU. Lo que hace cudaMalloc () es que asigna un puntero a la memoria (con espacio) en la GPU que luego es apuntado por el primer argumento que damos.


Todas las funciones API de CUDA devuelven un código de error (o cudaSuccess si no se produjo ningún error). Todos los demás parámetros se pasan por referencia. Sin embargo, en C simple no puede tener referencias, es por eso que debe pasar una dirección de la variable que desea que se almacene la información de retorno. Como devuelve un puntero, debe pasar un doble puntero.

Otra función conocida que opera en direcciones por la misma razón es la función scanf . ¿Cuántas veces se olvidó de escribir esto & antes de la variable a la que desea almacenar el valor? ;)

int i; scanf("%d",&i);