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


Actualmente estoy revisando 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 de GPU con el número 7 y copia los datos de la matriz de GPU en la matriz de 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 la declaración cudaMalloc((void**)&device_array, num_bytes); con un doble puntero? Incluso aquí definición de cudamalloc() on dice que el primer argumento es un doble puntero.

¿Por qué no simplemente devolver un puntero al principio de la memoria asignada en la GPU, al igual que la función malloc hace en la CPU?

Author: smilingbuddha, 2011-11-03

5 answers

Todas las funciones de la API 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 plano no puede tener referencias, por eso tiene que pasar una dirección de la variable que desea que se almacene la información de retorno. Dado que está devolviendo un puntero, debe pasar un doble puntero.

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

int i;
scanf("%d",&i);
 18
Author: CygnusX1,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-03 07:19:47

Esto es simplemente un horrible, horrible diseño de API. El problema con el paso de punteros dobles para una función de asignación que obtiene memoria abstracta (void *) es que tiene que hacer una variable temporal de tipo void * para mantener el resultado, luego asignarlo al puntero real del tipo correcto que desea usar. El casting, como en (void**)&device_array, no es válido C y resulta en un comportamiento indefinido. Simplemente debe escribir una función de envoltura que se comporta como normal malloc y devuelve un puntero, como en:

void *fixed_cudaMalloc(size_t len)
{
    void *p;
    if (cudaMalloc(&p, len) == success_code) return p;
    return 0;
}
 18
Author: R..,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-03 02:57:42

Lo lanzamos 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 de memoria (con espacio) en la GPU que luego es apuntado por el primer argumento que damos.

 7
Author: jwdmsd,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-03 12:09:47

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

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

 1
Author: flolo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-11-03 08:26:15

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 se puede almacenar 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 ser asignado, cudamalloc también requiere un puntero de doble vacío como su primer parámetro.

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

La razón detrás de la primera diferencia es que toda la función CUDA API sigue la convención de devolver un código de error entero. Así que para hacer las cosas consistentes, cudamalloc API también devuelve un entero.

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

En primer lugar, desde ya hemos decidido hacer que el cudamalloc devuelva un valor entero, ya no podemos usarlo para devolver la dirección de la memoria asignada. En C, la única otra forma 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 donde apunta el puntero. Los cambios a esos valores se pueden recuperar más tarde fuera del ámbito de la función utilizando la misma dirección de memoria.

Cómo el trabajos de 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
}

introduzca la descripción de la imagen aquí

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

Normalmente vivo en el mundo de python, así que también luché para entender por qué esto no funcionará.

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 asigna con el valor de la primera función argumento. No hay manera de que podamos recuperar el valor en esa variable local fuera del ámbito 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
}

introduzca la descripción de la imagen aquí

 1
Author: Louis T,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-31 03:08:48