J'ai eu un problème CUDA simple pour une affectation de classe, mais le professeur a ajouté une tâche facultative pour implémenter le même algorithme en utilisant la mémoire partagée à la place. Je ne pouvais pas le finir avant la date limite (comme dans, la date d'entrée était il y a une semaine) mais je suis toujours curieux alors maintenant je vais demander à Internet;). La tâche de base consistait à implémenter une version bastardisée d'une sur-relaxation successive rouge-noire à la fois séquentiellement et dans CUDA, assurez-vous d'avoir le même résultat dans les deux et de comparer ensuite l'accélération. Comme je l'ai dit, le faire avec de la mémoire partagée était un ajout facultatif de + 10%. Je vais poster ma version de travail et pseudocode ce que j'ai essayé de faire puisque je n'ai pas le code dans mes mains pour le moment, mais je peux le mettre à jour plus tard avec le code réel si quelqu'un a besoin il.CUDA: mémoire partagée sur un tableau 2D de grande taille
Avant que quelqu'un ne le dise: Oui, je sais que CUtil est boiteux, mais cela facilite la comparaison et les temporisations.
version mémoire globale de travail:
#include <stdlib.h>
#include <stdio.h>
#include <cutil_inline.h>
#define N 1024
__global__ void kernel(int *d_A, int *d_B) {
unsigned int index_x = blockIdx.x * blockDim.x + threadIdx.x;
unsigned int index_y = blockIdx.y * blockDim.y + threadIdx.y;
// map the two 2D indices to a single linear, 1D index
unsigned int grid_width = gridDim.x * blockDim.x;
unsigned int index = index_y * grid_width + index_x;
// check for boundaries and write out the result
if((index_x > 0) && (index_y > 0) && (index_x < N-1) && (index_y < N-1))
d_B[index] = (d_A[index-1]+d_A[index+1]+d_A[index+N]+d_A[index-N])/4;
}
main (int argc, char **argv) {
int A[N][N], B[N][N];
int *d_A, *d_B; // These are the copies of A and B on the GPU
int *h_B; // This is a host copy of the output of B from the GPU
int i, j;
int num_bytes = N * N * sizeof(int);
// Input is randomly generated
for(i=0;i<N;i++) {
for(j=0;j<N;j++) {
A[i][j] = rand()/1795831;
//printf("%d\n",A[i][j]);
}
}
cudaEvent_t start_event0, stop_event0;
float elapsed_time0;
CUDA_SAFE_CALL(cudaEventCreate(&start_event0));
CUDA_SAFE_CALL(cudaEventCreate(&stop_event0));
cudaEventRecord(start_event0, 0);
// sequential implementation of main computation
for(i=1;i<N-1;i++) {
for(j=1;j<N-1;j++) {
B[i][j] = (A[i-1][j]+A[i+1][j]+A[i][j-1]+A[i][j+1])/4;
}
}
cudaEventRecord(stop_event0, 0);
cudaEventSynchronize(stop_event0);
CUDA_SAFE_CALL(cudaEventElapsedTime(&elapsed_time0,start_event0, stop_event0));
h_B = (int *)malloc(num_bytes);
memset(h_B, 0, num_bytes);
//ALLOCATE MEMORY FOR GPU COPIES OF A AND B
cudaMalloc((void**)&d_A, num_bytes);
cudaMalloc((void**)&d_B, num_bytes);
cudaMemset(d_A, 0, num_bytes);
cudaMemset(d_B, 0, num_bytes);
//COPY A TO GPU
cudaMemcpy(d_A, A, num_bytes, cudaMemcpyHostToDevice);
// create CUDA event handles for timing purposes
cudaEvent_t start_event, stop_event;
float elapsed_time;
CUDA_SAFE_CALL(cudaEventCreate(&start_event));
CUDA_SAFE_CALL(cudaEventCreate(&stop_event));
cudaEventRecord(start_event, 0);
// TODO: CREATE BLOCKS AND THREADS AND INVOKE GPU KERNEL
dim3 block_size(256,1,1); //values experimentally determined to be fastest
dim3 grid_size;
grid_size.x = N/block_size.x;
grid_size.y = N/block_size.y;
kernel<<<grid_size,block_size>>>(d_A,d_B);
cudaEventRecord(stop_event, 0);
cudaEventSynchronize(stop_event);
CUDA_SAFE_CALL(cudaEventElapsedTime(&elapsed_time,start_event, stop_event));
//COPY B BACK FROM GPU
cudaMemcpy(h_B, d_B, num_bytes, cudaMemcpyDeviceToHost);
// Verify result is correct
CUTBoolean res = cutComparei((int *)B, (int *)h_B, N*N);
printf("Test %s\n",(1 == res)?"Passed":"Failed");
printf("Elapsed Time for Sequential: \t%.2f ms\n", elapsed_time0);
printf("Elapsed Time for CUDA:\t%.2f ms\n", elapsed_time);
printf("CUDA Speedup:\t%.2fx\n",(elapsed_time0/elapsed_time));
cudaFree(d_A);
cudaFree(d_B);
free(h_B);
cutilDeviceReset();
}
Pour la version de mémoire partagée, ce que j'ai essayé jusqu'à présent:
#define N 1024
__global__ void kernel(int *d_A, int *d_B, int width) {
//assuming width is 64 because that's the biggest number I can make it
//each MP has 48KB of shared mem, which is 12K ints, 32 threads/warp, so max 375 ints/thread?
__shared__ int A_sh[3][66];
//get x and y index and turn it into linear index
for(i=0; i < width+2; i++) //have to load 2 extra values due to the -1 and +1 in algo
A_sh[index_y%3][i] = d_A[index+i-1]; //so A_sh[index_y%3][0] is actually d_A[index-1]
__syncthreads(); //and hope that previous and next row have been loaded by other threads in the block?
//ignore boundary conditions because it's pseudocode
for(i=0; i < width; i++)
d_B[index+i] = A_sh[index_y%3][i] + A_sh[index_y%3][i+2] + A_sh[index_y%3-1][i+1] + A_sh[index_y%3+1][i+1];
}
main(){
//same init as above until threads/grid init
dim3 threadsperblk(32,16);
dim3 numblks(32,64);
kernel<<<numblks,threadsperblk>>>(d_A,d_B,64);
//rest is the same
}
Ce plantages de code mem partagé ("lancement a échoué en raison de erreur non spécifiée ") puisque je n'ai pas encore pris toutes les conditions aux limites, mais je ne m'inquiète pas autant que de trouver la bonne façon de faire avancer les choses. Je pense que mon code est trop compliqué pour être le bon chemin (surtout par rapport aux exemples du SDK), mais je ne peux pas non plus voir une autre façon de le faire puisque mon tableau ne rentre pas dans mem comme tous les exemples peuvent trouver.
Et franchement, je ne suis pas sûr que ce serait beaucoup plus rapide sur mon matériel (GTX 560 Ti - exécute la version de la mémoire globale en 0.121ms), mais je dois me prouver d'abord: P
Édition 2: Pour ceux qui courent à travers cela à l'avenir, le code dans la réponse est un bon point de départ si vous voulez faire de la mémoire partagée.
n'y avait pas pensé de cette façon, merci. La question est, comment puis-je garder les threads dans le bloc de marcher les uns sur les autres? Dis que j'ai 2 fils dans un bloc, et que le fil 2 veut charger la ligne (f) alors que le fil 1 travaille encore sur la ligne (c)? Ou devrais-je juste changer le code pour avoir 1 thread par bloc et ensuite avoir plusieurs blocs? – a5ehren
@ a5ehren: Il existe une primitive de synchronisation intra-bloc appelée __syncthreads() que vous pouvez utiliser pour synchroniser les threads. Idéalement, vous voulez un multiple de 32 threads par bloc, et autant de blocs que nécessaire pour couvrir la largeur de ligne de l'espace d'entrée. Je peux ajouter un petit pseudocode à la réponse si vous voulez plus d'aide. – talonmies
Ainsi, chaque thread chargerait-il sa partie de la ligne, la synchroniserait, et supposerait qu'il y a des threads qui travaillent les lignes au-dessus et au-dessous? Je suppose que certains pseudo-codes aideraient: P – a5ehren