Verwendung von cudamalloc(). Warum der Doppelzeiger?

Lesezeit: 9 Minuten

Ich gehe gerade die Tutorial-Beispiele durch http://code.google.com/p/stanford-cs193g-sp2010/ CUDA zu lernen. Der Code, der demonstriert __global__ Funktionen ist unten angegeben. Es erstellt einfach zwei Arrays, eines auf der CPU und eines auf der GPU, füllt das GPU-Array mit der Nummer 7 und kopiert die GPU-Array-Daten in das CPU-Array.

#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);
} 

Meine Frage ist, warum sie das formuliert haben cudaMalloc((void**)&device_array, num_bytes); Anweisung mit einem Doppelzeiger? Eben hier Die Definition von cudamalloc() besagt, dass das erste Argument ein Doppelzeiger ist.

Warum nicht einfach einen Zeiger auf den Anfang des zugewiesenen Speichers auf der GPU zurückgeben, genau wie die malloc funktioniert auf der CPU?

  • Weil es einen Fehlercode zurückgibt, der Ihnen sagt, warum es fehlgeschlagen ist. Das Zurückgeben eines Nullzeigers bei einem Fehler, wie malloc(), ist ein schlechter Ersatz für einen Fehlercode und bedeutet nicht mehr als “es hat nicht funktioniert”. Du sollst es überprüfen.

    – Hans Passant

    3. November 2011 um 0:57 Uhr

  • @Hans: Es ist immer noch ein schreckliches API-Design. Stattdessen sollte es extra dauern int *error Argument zum Speichern des Fehlercodes, der gültig ist, wenn der Rückgabewert ein Nullzeiger ist. So wie es ist, negiert das Design alle Vorteile von void Zeiger und erfordert, dass Sie durch Reifen springen, um die Funktion richtig zu verwenden.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    3. November 2011 um 2:59 Uhr

  • @R.: Sie erhalten Anerkennung dafür, dass Sie eine Alternative anbieten, während Sie gleichzeitig die API kritisieren – die meisten API-Kritiker flügeln, ohne Alternativen vorzuschlagen -, aber es sei denn, Sie glauben, dass jeder CUDA-Laufzeitaufruf ein zusätzliches int * benötigen sollte, um einen Fehlercode zurückzugeben, (was zu einer viel unübersichtlicheren und schwieriger zu verwendenden API führen würde), ist Ihr alternativer Vorschlag nicht orthogonal und verstößt gegen das Prinzip der geringsten Überraschung.

    – ArchaeaSoftware

    3. November 2011 um 11:58 Uhr

  • Der Verstoß gegen das Prinzip des geringsten Erstaunens ist ein viel kleineres Vergehen als die Forderung nach Aushilfe void *ist überall. Das int *error könnte null sein, wenn der Benutzer sich nicht um den Grund kümmert. Eigentlich sehe ich keinen anderen Grund, warum die Zuordnung fehlschlagen könnte als “nicht genügend Speicher” (und was noch wichtiger ist, keinen Grund, warum es den Aufrufer interessieren könnte, warum es fehlgeschlagen ist), also ist es wahrscheinlich zunächst nur ein Designfehler.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    3. November 2011 um 12:31 Uhr


  • mögliches Duplikat von Warum verwendet cudaMalloc() Zeiger auf Zeiger?

    – chappjc

    23. Mai 2015 um 15:43 Uhr

Alle CUDA-API-Funktionen geben einen Fehlercode zurück (oder cudaSuccess, wenn kein Fehler aufgetreten ist). Alle anderen Parameter werden per Referenz übergeben. In Plain C können Sie jedoch keine Referenzen haben, deshalb müssen Sie eine Adresse der Variablen übergeben, in der die Rückgabeinformationen gespeichert werden sollen. Da Sie einen Zeiger zurückgeben, müssen Sie einen Doppelzeiger übergeben.

Eine weitere bekannte Funktion, die aus demselben Grund auf Adressen angewendet wird, ist die scanf Funktion. Wie oft hast du vergessen, das zu schreiben & vor der Variable, in der Sie den Wert speichern möchten? 😉

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

  • Wird der Doppelzeiger in CUDA nur benötigt, weil die API den Zeiger zweimal dereferenziert, um den Zeiger auf den im Gerätespeicher gespeicherten Datentyp zu erhalten, und den anderen, um tatsächlich auf den Speicherinhalt zuzugreifen?

    – rgk

    20. Januar 2015 um 13:57 Uhr

  • Es wird wegen der Funktion benötigt setzt der Zeiger. Wie bei allen Ausgabeparametern in C benötigen Sie einen Zeiger auf eine tatsächliche Variable, die Sie setzen, und nicht den Wert selbst.

    – CygnusX1

    20. Januar 2015 um 18:42 Uhr

Das ist einfach ein schreckliches, schreckliches API-Design. Das Problem beim Übergeben von Doppelzeigern für eine Zuordnungsfunktion, die abstrakte (void *) Speicher ist, dass Sie eine temporäre Variable vom Typ erstellen müssen void * um das Ergebnis zu halten, weisen Sie es dann dem echten Zeiger des richtigen Typs zu, den Sie verwenden möchten. Gießen, wie in (void**)&device_array, ist ungültiges C und führt zu undefiniertem Verhalten. Sie sollten einfach eine Wrapper-Funktion schreiben, die sich normal verhält malloc und gibt einen Zeiger zurück, wie in:

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

  • Ich glaube, CUDART hat einen Vorlagen-Wrapper für cudaMalloc(), der die (void **)-Umwandlung unnötig macht. Außerdem würde ich die hier angegebene Funktion nicht empfehlen, sie in Produktion zu bringen; es verbirgt zu viele nützliche Informationen, die vom Rückgabewert von cudaMalloc() bereitgestellt werden.

    – ArchaeaSoftware

    3. November 2011 um 11:56 Uhr

  • Um einen Ausdruck zu prägen, wäre das „einfach ein schreckliches, schreckliches API-Design“. Es ist besser, einen konsistenten Rückgabewert über die verschiedenen APIs hinweg zu haben und den zugewiesenen Zeiger zurückzugeben; und dann sind Sie wieder da, wo wir angefangen haben.

    – ArchaeaSoftware

    10. Dezember 2012 um 14:50 Uhr

  • Wie würden Sie dann sowohl den Fehlercode als auch den Zeiger zurückgeben? Beachten Sie, dass die Fehlerbehandlung dem Benutzer der API überlassen werden sollte, also it hat zurückzusenden.

    – CygnusX1

    19. Februar 2013 um 15:57 Uhr

  • @CygnusX1: Sicher könntest du. Der Rückgabewert wäre bei einem Fehler ein Nullzeiger. Der einzige Grund, das überhaupt zu verwenden errorp Argument wäre, wenn Sie das wissen möchten Grund Die Zuweisung ist fehlgeschlagen, aber der Grund ist sowieso immer eine Variante von “nicht genügend Speicher verfügbar”, also ist es ziemlich nutzlos. Normalerweise möchten Sie den Grund nicht verwenden, bis Innerhalb der Körper der ifum zB die Fehlermeldung auszudrucken.

    – R.. GitHub HÖR AUF, EIS ZU HELFEN

    15. Mai 2013 um 13:33 Uhr

  • Stimmt, für diese Funktion kann es so funktionieren, aber nicht für alle Funktionen wäre eine NULL ein Fehler. Ich stimme Ihrer Aussage über “dumme Konsistenz” absolut nicht zu. Auf längere Sicht führt mangelnde Konsistenz zu einem Durcheinander. Compiler-Fehlermeldungen können Ihnen dabei helfen, Fehler sofort zu erkennen, aber diese Fehler von vornherein zu treffen, verlangsamt den Entwicklungsprozess und hilft Ihnen nicht, den bereits vorhandenen Code zu lesen! Menschen sind begrenzt und es wird nicht funktionieren, sie zu bitten, sich an eine inkonsistente API zu erinnern. Bin ich dir deswegen „kleinmütig“? Ich finde es etwas beleidigend.

    – CygnusX1

    15. Mai 2013 um 16:54 Uhr

Benutzeravatar von Louis T
Ludwig T

In C/C++ können Sie einen Speicherblock zur Laufzeit dynamisch zuweisen, indem Sie die aufrufen malloc Funktion.

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

Das malloc Die Funktion gibt die Adresse des zugewiesenen Speicherblocks zurück, die in einer Variablen oder einer Art Zeiger gespeichert werden kann.
Die Speicherzuweisung in CUDA ist in zweierlei Hinsicht etwas anders:

  1. Das cudamalloc statt eines Zeigers auf den Speicherblock eine ganze Zahl als Fehlercode zurückgeben.
  2. Neben der zuzuweisenden Bytegröße cudamalloc erfordert außerdem einen doppelten void-Zeiger als ersten Parameter.

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

Der Grund für den ersten Unterschied ist, dass alle CUDA-API-Funktionen der Konvention folgen, einen ganzzahligen Fehlercode zurückzugeben. Um die Dinge konsistent zu machen, cudamalloc Die API gibt auch eine ganze Zahl zurück.

Die Anforderungen für einen Doppelzeiger als erstes Funktionsargument können in zwei Schritten verstanden werden.

Erstens, da wir bereits entschieden haben, dass cudamalloc einen ganzzahligen Wert zurückgeben soll, können wir es nicht mehr verwenden, um die Adresse des zugewiesenen Speichers zurückzugeben. In C besteht die einzige andere Kommunikationsmöglichkeit für eine Funktion darin, den Zeiger oder die Adresse an die Funktion zu übergeben. Die Funktion kann Änderungen an dem Wert vornehmen, der an der Adresse gespeichert ist, oder an der Adresse, auf die der Zeiger zeigt. Die Änderungen an diesen Werten können später außerhalb des Funktionsbereichs abgerufen werden, indem dieselbe Speicheradresse verwendet wird.

wie der Doppelzeiger funktioniert

Das folgende Diagramm veranschaulicht, wie es mit dem Doppelzeiger funktioniert.

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

Geben Sie hier die Bildbeschreibung ein

Wozu brauchen wir den Doppelzeiger? Warum das funktioniert

Ich lebe normalerweise in der Python-Welt, also hatte ich auch Mühe zu verstehen, warum dies nicht funktionieren wird.

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

Warum funktioniert es also nicht? Denn in C, wann cudamalloc aufgerufen wird, wird eine lokale Variable namens d_array erstellt und ihr der Wert des ersten Funktionsarguments zugewiesen. Es gibt keine Möglichkeit, den Wert in dieser lokalen Variablen außerhalb des Gültigkeitsbereichs der Funktion abzurufen. Deshalb brauchen wir hier einen Zeiger auf einen Zeiger.

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

Geben Sie hier die Bildbeschreibung ein

Wir wandeln es in einen Doppelzeiger um, weil es ein Zeiger auf den Zeiger ist. Es muss auf einen Zeiger des GPU-Speichers zeigen. Was cudaMalloc() macht, ist, dass es einen Speicherzeiger (mit Platz) auf der GPU zuweist, auf den dann das erste Argument zeigt, das wir geben.

Flolos Benutzeravatar
Flolo

Das Problem: Sie müssen zwei Werte zurückgeben: Returncode UND Zeiger auf Speicher (falls Returncode Erfolg anzeigt). Sie müssen also einen davon zu einem Zeiger machen, um den Typ zurückzugeben. Und als Rückgabetyp haben Sie die Wahl zwischen Rückgabezeiger auf int (für Fehlercode) oder Rückgabezeiger auf Zeiger (für Speicheradresse). Da ist eine Lösung so gut wie die andere (und eine davon ergibt den Zeiger auf den Zeiger (ich ziehe es vor, diesen Begriff anstelle von zu verwenden Doppelzeigerda dies eher wie ein Zeiger auf eine doppelte Gleitkommazahl klingt)).

In malloc haben Sie die nette Eigenschaft, dass Sie Nullzeiger haben können, um einen Fehler anzuzeigen, sodass Sie im Grunde nur einen Rückgabewert benötigen. Ich bin mir nicht sicher, ob dies mit einem Zeiger auf den Gerätespeicher möglich ist, da dies möglicherweise der Fall ist kein oder ein falscher Nullwert (zur Erinnerung: Das ist CUDA und NICHT Ansi C). Es könnte sein, dass der Null-Zeiger auf dem Hostsystem völlig anders ist als die für das Gerät verwendete Null, und daher funktioniert die Rückgabe des Null-Zeigers zur Anzeige von Fehlern nicht, und Sie müssen die API so gestalten (das würde auch bedeuten dass Sie auf beiden Geräten KEINE gemeinsame NULL haben).

1410780cookie-checkVerwendung von cudamalloc(). Warum der Doppelzeiger?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy