¿Por qué cudaMalloc () usa puntero a puntero?


Por ejemplo, cudaMalloc((void**)&device_array, num_bytes);

Esta pregunta se ha hecho antes, y la respuesta fue "porque cudaMalloc devuelve un código de error", pero no lo entiendo - ¿qué tiene que ver un doble puntero con devolver un código de error? ¿Por qué un simple puntero no puede hacer el trabajo?

Si escribo

cudaError_t catch_status;
catch_status = cudaMalloc((void**)&device_array, num_bytes);

El código de error se colocará en catch_status, y devolver un simple puntero a la memoria GPU asignada debería ser suficiente, ¿no?

Author: Community, 2012-10-17

3 answers

En C, los datos se pueden pasar a las funciones por valor o a través de referencia de paso simulado (es decir, mediante un puntero a los datos). By value es una metodología unidireccional, by pointer permite el flujo de datos bidireccional entre la función y su entorno de llamada.

Cuando se pasa un elemento de datos a una función a través de la lista de parámetros de función, y se espera que la función modifique el elemento de datos original para que el valor modificado aparezca en el entorno de llamada, el método C correcto para esto es pasar el elemento de datos por puntero. En C, cuando pasamos por puntero, tomamos la dirección del elemento a modificar, creando un puntero (quizás un puntero a un puntero en este caso) y pasamos la dirección a la función. Esto permite a la función modificar el elemento original (a través del puntero) en el entorno de llamada.

Normalmente malloc devuelve un puntero, y podemos usar la asignación en el entorno de llamada para asignar este valor devuelto al puntero deseado. En el caso de cudaMalloc, Los diseñadores de CUDA eligieron usar el valor devuelto para llevar un estado de error en lugar de un puntero. Por lo tanto, el ajuste del puntero en el entorno de llamada debe ocurrir a través de uno de los parámetros pasados a la función, por referencia (es decir, por puntero). Dado que es un valor de puntero que queremos establecer, debemos tomar la dirección del puntero (creando un puntero a un puntero) y pasar esa dirección a la función cudaMalloc.

 60
Author: Robert Crovella,
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
2015-12-05 19:51:52

Añadiendo a la respuesta de Robert, pero para reiterar primero, es una API C, lo que significa que no soporta referencias , lo que le permitiría modificar el valor de un puntero (no solo a lo que se apunta) dentro de la función. La respuesta de Robert Crovella explicó esto. También tenga en cuenta que tiene que ser void porque C tampoco soporta la sobrecarga de funciones.

Además, cuando se utiliza una API de C dentro de un programa de C++ (pero no ha indicado esto), es común envolver función en una plantilla. Por ejemplo,

template<typename T>
cudaError_t cudaAlloc(T*& d_p, size_t elements)
{
    return cudaMalloc((void**)&d_p, elements * sizeof(T));
}

Hay dos diferencias con la forma en que llamarías a la función anterior cudaAlloc:

  1. Pase el puntero del dispositivo directamente, sin usar la dirección del operador (&) cuando lo llame, y sin lanzar a un tipo void.
  2. El segundo argumento elements es ahora el número de elementos en lugar del número de bytes. El operador sizeof facilita esto. Esto es posiblemente más intuitivo para especificar elementos y no preocuparse por byte.

Por ejemplo:

float *d = nullptr;  // floats, 4 bytes per elements
size_t N = 100;      // 100 elements

cudaError_t err = cudaAlloc(d,N);      // modifies d, input is not bytes

if (err != cudaSuccess)
    std::cerr << "Unable to allocate device memory" << std::endl;
 9
Author: chappjc,
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
2015-12-05 05:23:16

Supongo que la firma de la función cudaMalloc podría explicarse mejor con un ejemplo. Se trata básicamente de asignar un búfer a través de un puntero a ese búfer (un puntero a puntero), como el siguiente método:

int cudaMalloc(void **memory, size_t size)
{
    int errorCode = 0;

    *memory = new char[size];

    return errorCode;
}

Como puede ver, el método toma un memory puntero a puntero, en el que guarda la nueva memoria asignada. Luego devuelve el código de error (en este caso como un entero, pero en realidad es una enumeración).

La función cudaMalloc podría diseñarse de la siguiente manera también:

void * cudaMalloc(size_t size, int * errorCode = nullptr)
{
    if(errorCode)
        errorCode = 0;

    char *memory = new char[size];

    return memory;
}

En este segundo caso, el código de error se establece a través de un puntero implícito establecido en null (para el caso de que las personas no se molesten en absoluto con el código de error). Luego se devuelve la memoria asignada.

El primer método se puede utilizar como es el actual cudaMalloc en este momento:

float *p;
int errorCode;
errorCode = cudaMalloc((void**)&p, sizeof(float));

Mientras que el segundo se puede usar de la siguiente manera:

float *p;
int errorCode;
p = (float *) cudaMalloc(sizeof(float), &errorCode);

Estos dos métodos son funcionalmente equivalentes, mientras que tienen diferentes firmas, y la gente de cuda decidió ir para el primer método, devolver el código de error y asignar la memoria a través de un puntero, mientras que la mayoría de la gente dice que el segundo método habría sido una mejor opción.

 2
Author: meJustAndrew,
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-05-18 20:51:02