Je suis en train de parcourir les exemples de didacticiels sur http://code.google.com/p/stanford-cs193g-sp2010/ pour apprendre CUDA. Le code qui démontre les fonctions __global__
Est donné ci-dessous. Il crée simplement deux matrices, une sur le CPU et une sur le GPU, remplit la matrice GPU avec le numéro 7 et copie les données de la matrice GPU dans la matrice 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);
}
Ma question est pourquoi ont-ils formulé l'instruction cudaMalloc((void**)&device_array, num_bytes);
avec un double pointeur? Même ici définition de cudamalloc () on dit que le premier argument est un double pointeur.
Pourquoi ne pas simplement renvoyer un pointeur sur le début de la mémoire allouée sur le GPU, tout comme la fonction malloc
sur le CPU?
Toutes les fonctions de l'API CUDA renvoient un code d'erreur (ou cudaSuccess si aucune erreur ne s'est produite). Tous les autres paramètres sont transmis par référence. Cependant, en clair C, vous ne pouvez pas avoir de références, c'est pourquoi vous devez passer une adresse de la variable que vous souhaitez que les informations de retour soient stockées. Puisque vous renvoyez un pointeur, vous devez passer un double pointeur.
Une autre fonction bien connue qui fonctionne sur les adresses pour la même raison est la fonction scanf
. Combien de fois avez-vous oublié d'écrire ce &
avant la variable dans laquelle vous souhaitez stocker la valeur? ;)
int i;
scanf("%d",&i);
Il s'agit simplement d'une conception d'API horrible et horrible. Le problème avec le passage de pointeurs doubles pour une fonction d'allocation qui obtient un résumé (void *
) la mémoire est que vous devez créer une variable temporaire de type void *
pour conserver le résultat, puis affectez-le au pointeur réel du type correct que vous souhaitez utiliser. Casting, comme dans (void**)&device_array
, n'est pas C valide et entraîne un comportement indéfini. Vous devez simplement écrire une fonction wrapper qui se comporte comme un malloc
normal et renvoie un pointeur, comme dans:
void *fixed_cudaMalloc(size_t len)
{
void *p;
if (cudaMalloc(&p, len) == success_code) return p;
return 0;
}
Nous le convertissons en double pointeur car c'est un pointeur vers le pointeur. Il doit pointer vers un pointeur de mémoire GPU. Ce que fait cudaMalloc (), c'est qu'il alloue un pointeur de mémoire (avec de l'espace) sur le GPU qui est ensuite pointé par le premier argument que nous donnons.
En C/C++, vous pouvez allouer dynamiquement un bloc de mémoire lors de l'exécution en appelant la fonction malloc
.
int * h_array
h_array = malloc(sizeof(int))
La fonction malloc
renvoie l'adresse du bloc de mémoire alloué qui peut être stockée dans une variable d'une sorte de pointeur.
L'allocation de mémoire dans CUDA est un peu différente de deux manières,
cudamalloc
renvoie un entier comme code d'erreur au lieu d'un pointeur vers le bloc de mémoire.En plus de la taille d'octet à allouer, cudamalloc
requiert également un pointeur à double vide comme premier paramètre.
int * d_array cudamalloc ((void **) & d_array, sizeof (int))
La raison de la première différence est que toutes les fonctions de l'API CUDA suivent la convention de retour d'un code d'erreur entier. Donc, pour rendre les choses cohérentes, l'API cudamalloc
renvoie également un entier.
Il y a des exigences pour un pointeur double comme premier argument de la fonction peut être compris en deux étapes.
Tout d'abord, comme nous avons déjà décidé de faire en sorte que le cudamalloc retourne une valeur entière, nous ne pouvons plus l'utiliser pour renvoyer l'adresse de la mémoire allouée. En C, la seule autre façon pour une fonction de communiquer est de passer le pointeur ou l'adresse à la fonction. La fonction peut apporter des modifications à la valeur stockée à l'adresse ou à l'adresse vers laquelle le pointeur pointe. Les modifications apportées à ces valeurs peuvent être récupérées ultérieurement en dehors de la portée de la fonction en utilisant la même adresse mémoire.
Le diagramme suivant illustre son fonctionnement avec le double pointeur.
int cudamalloc((void **) &d_array, int type_size) {
*d_array = malloc(type_size)
return return_code
}
Je vis normalement le monde python donc j'ai également eu du mal à comprendre pourquoi cela ne fonctionnerait pas.
int cudamalloc((void *) d_array, int type_size) {
d_array = malloc(type_size)
...
return error_status
}
Alors pourquoi ça ne marche pas? Parce qu'en C, lorsque cudamalloc
est appelé, une variable locale nommée d_array est créée et affectée avec la valeur du premier argument de fonction. Il n'y a aucun moyen de récupérer la valeur dans cette variable locale en dehors de la portée de la fonction. C'est pourquoi nous avons besoin d'un pointeur vers un pointeur ici.
int cudamalloc((void *) d_array, int type_size) {
*d_array = malloc(type_size)
...
return return_code
}
Le problème: vous devez renvoyer deux valeurs: Code de retour ET pointeur vers la mémoire (dans le cas où le code de retour indique le succès). Vous devez donc en faire un pointeur pour renvoyer le type. Et comme type de retour, vous avez le choix entre retourner le pointeur sur int (pour le code d'erreur) ou retourner le pointeur sur le pointeur (pour l'adresse mémoire). Là, une solution est aussi bonne que l'autre (et l'une d'elles donne le pointeur à pointeur (je préfère utiliser ce terme au lieu de double pointeur , comme cela ressemble plus à un pointeur vers un double nombre à virgule flottante)).
Dans malloc, vous avez la propriété Nice que vous pouvez avoir des pointeurs nuls pour indiquer une erreur, donc vous avez essentiellement besoin d'une seule valeur de retour .. Je ne sais pas si cela est possible avec un pointeur sur la mémoire de l'appareil, car il se peut qu'il y ait non ou une valeur nulle incorrecte (rappelez-vous: c'est CUDA et PAS Ansi C). Il se peut que le pointeur null sur le système hôte soit entièrement différent du null utilisé pour le périphérique, et en tant que tel, le retour du pointeur null pour indiquer des erreurs ne fonctionne pas, et vous devez faire l'API de cette façon (cela signifierait également que vous n'avez PAS de NULL commun sur les deux appareils).