c++ api - Perché cudaMalloc()usa il puntatore al puntatore?




example starting (4)

Ad esempio, cudaMalloc((void**)&device_array, num_bytes);

Questa domanda è stata asked prima e la risposta è stata "perché cudaMalloc restituisce un codice di errore", ma non capisco - cosa ha a che fare un doppio puntatore con la restituzione di un codice di errore? Perché un puntatore semplice non può fare il lavoro?

Se scrivo

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

il codice di errore verrà inserito in catch_status e restituire un semplice puntatore alla memoria della GPU allocata dovrebbe essere sufficiente, non dovrebbe?


Answers

Aggiungendo la risposta di Robert, ma per prima cosa reiterare, si tratta di un'API C, il che significa che non supporta i riferimenti, il che consentirebbe di modificare il valore di un puntatore (non solo quello che viene indicato) all'interno della funzione . La risposta di Robert Crovella lo ha spiegato. Si noti inoltre che deve essere void perché anche C non supporta l'overloading delle funzioni.

Inoltre, quando si utilizza un'API C all'interno di un programma C ++ (ma non lo si è detto), è comune avvolgere tale funzione in un modello. Per esempio,

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

Esistono due differenze con il modo in cui si chiamerebbe la funzione cudaAlloc sopra cudaAlloc :

  1. Passa direttamente il puntatore del dispositivo, senza utilizzare l'operatore address-of ( & ) quando lo chiami e senza eseguire il cast di un tipo void .
  2. Gli elements secondo argomento sono ora il numero di elementi anziché il numero di byte. L'operatore sizeof facilita questo. Questo è probabilmente più intuitivo per specificare gli elementi e non preoccuparsi dei byte.

Per esempio:

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;

In C, i dati possono essere trasferiti alle funzioni in base al valore o tramite un riferimento pass-by simulato (cioè con un puntatore ai dati). Per valore è una metodologia unidirezionale, tramite puntatore consente il flusso di dati bidirezionale tra la funzione e il suo ambiente di chiamata.

Quando un elemento di dati viene passato a una funzione tramite l'elenco dei parametri di funzione e si prevede che la funzione modifichi l'elemento di dati originale in modo che il valore modificato venga visualizzato nell'ambiente di chiamata, il metodo C corretto per questo è passare l'elemento di dati da puntatore. In C, quando passiamo per puntatore, prendiamo l'indirizzo dell'elemento da modificare, creando un puntatore (forse un puntatore a un puntatore in questo caso) e consegnando l'indirizzo alla funzione. Ciò consente alla funzione di modificare l'elemento originale (tramite il puntatore) nell'ambiente di chiamata.

Normalmente malloc restituisce un puntatore e possiamo utilizzare l'assegnazione nell'ambiente chiamante per assegnare questo valore restituito al puntatore desiderato. Nel caso di cudaMalloc , i progettisti CUDA hanno scelto di utilizzare il valore restituito per portare uno stato di errore anziché un puntatore. Pertanto l'impostazione del puntatore nell'ambiente chiamante deve avvenire tramite uno dei parametri passati alla funzione, per riferimento (ad es. Tramite puntatore). Poiché è un valore puntatore che vogliamo impostare, dobbiamo prendere l'indirizzo del puntatore (creando un puntatore a un puntatore) e passare quell'indirizzo alla funzione cudaMalloc .


Immagino che la firma della funzione cudaMalloc possa essere meglio spiegata da un esempio. Assegna fondamentalmente un buffer attraverso un puntatore a quel buffer (un puntatore al puntatore), come il seguente metodo:

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

    *memory = new char[size];

    return errorCode;
}

Come puoi vedere, il metodo accetta un puntatore di memory sul puntatore, sul quale salva la nuova memoria allocata. Quindi restituisce il codice di errore (in questo caso come numero intero, ma in realtà è un enum).

La funzione cudaMalloc potrebbe essere progettata come segue anche:

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

    char *memory = new char[size];

    return memory;
}

In questo secondo caso, il codice di errore viene impostato tramite un puntatore implicito impostato su null (nel caso in cui le persone non si preoccupino affatto del codice di errore). Quindi viene restituita la memoria allocata.

Il primo metodo può essere usato come è il cudaMalloc attuale in questo momento:

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

Mentre il secondo può essere usato come segue:

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

Questi due metodi sono funzionalmente equivalenti, mentre hanno diverse firme, e le persone di cuda hanno deciso di utilizzare il primo metodo, restituendo il codice di errore e assegnando la memoria tramite un puntatore, mentre la maggior parte delle persone afferma che il secondo metodo sarebbe stato un scelta migliore


Un puntatore fa riferimento direttamente alla posizione di memoria di un oggetto. Java non ha nulla di simile. Java ha riferimenti che fanno riferimento alla posizione dell'oggetto attraverso le tabelle hash. Non puoi fare nulla come l'aritmetica dei puntatori in Java con questi riferimenti.

Per rispondere alla tua domanda, è solo una tua preferenza. Preferisco usare la sintassi simile a Java.





c++ c pointers cuda