在配置GPU时一般都看重其的架构,流处理器数,以及显存数。 以英伟达的GPU为例架构一般以科学家的名字来命名,如Fermi(费米),Kepler(开普勒),现在主流的Maxwell(麦克斯韦),Pascal(帕斯卡),不同的架构主要体现在如纹理单元,流处理器,带宽等较为底层的东西不同,为线程与块中主要关心的是其流多处理器(streaming multiprocessor,SM)以及一个流多处理器包含的多个流处理器(scalar processor,SP) 或称为CUDA核(CUDA core)。当控制器将一个线程分配给一个流多处理器后,流多处理器的核协调工作,并行处理所有的线程。 在并行运算时,会出现网格(grid),线程块(block),线程(thread)三个常见的概念。一个网格下包含一个或多个线程块,一个线程块下包含多个线程。 在使用时 block和grid都可以用三维的向量表示,其中block向量的元素是thread,grid的元素是block。当然线程块内的线程数不是无限的,如先前的G80一个线程块内线程数最多为512个,Fermi架构下的线程块的线程数增加到1024个,Kepler架构下的线程数达到了2048个。
如图中grid block,可用dim3来表示三维的向量:
dim3 `gridSize(3×2×1);` dim3 blockSize(2,2,2); kernel<<<gridSize,blockSize>>>();如需二维,则定义时将第三元缺省,如果需一维,则可以直接用整型量int来表示线程和块数; 在核函数kernel中线程是并行执行的,而当一个线程中需要用到另一个线程的量时就需要用到线程的索引: grid内每个block的位置可以通过blockIdx变量来获得,block的大小可以由变量blockDim来获得,thread在block中位置可以由threadId来获得;
下面再来看先前的两个数组相加的程序,这次我们加上线程索引和块索引;并且将数组的大小扩大到1000:
#include "cuda_runtime.h" #include "device_launch_parameters.h" #include<stdlib.h> #include <stdio.h> cudaError_t addWithCuda(int *c, int *a, int *b, unsigned int size); __global__ void addKernel(int *c, int *a, int *b) { int i = threadIdx.x+blockIdx.x*blockDim.x; c[i] = a[i] + b[i]; } int main() { const int arraySize = 1000; int a[arraySize]; int b[arraySize]; int c[arraySize] = { 0 }; for (int i = 0; i < arraySize; i++) { a[i] = rand() % 100; b[i] = rand() % 100; } // Add vectors in parallel. cudaError_t cudaStatus = addWithCuda(c, a, b, arraySize); if (cudaStatus != cudaSuccess) { fprintf(stderr, "addWithCuda failed!"); return 1; } for (int i = 0; i < 1000; i++) { printf("c[%d]=%d\n", i, c[i]); } getchar(); // cudaDeviceReset must be called before exiting in order for profiling and // tracing tools such as Nsight and Visual Profiler to show complete traces. cudaStatus = cudaDeviceReset(); if (cudaStatus != cudaSuccess) { fprintf(stderr, "cudaDeviceReset failed!"); return 1; } return 0; } // Helper function for using CUDA to add vectors in parallel. cudaError_t addWithCuda(int *c, int *a, int *b, unsigned int size) { int *dev_a = 0; int *dev_b = 0; int *dev_c = 0; cudaError_t cudaStatus; // Choose which GPU to run on, change this on a multi-GPU system. cudaStatus = cudaSetDevice(0); if (cudaStatus != cudaSuccess) { fprintf(stderr, "cudaSetDevice failed! Do you have a CUDA-capable GPU installed?"); goto Error; } // Allocate GPU buffers for three vectors (two input, one output) . cudaStatus = cudaMalloc((void**)&dev_c, size * sizeof(int)); if (cudaStatus != cudaSuccess) { fprintf(stderr, "cudaMalloc failed!"); goto Error; } cudaStatus = cudaMalloc((void**)&dev_a, size * sizeof(int)); if (cudaStatus != cudaSuccess) { fprintf(stderr, "cudaMalloc failed!"); goto Error; } cudaStatus = cudaMalloc((void**)&dev_b, size * sizeof(int)); if (cudaStatus != cudaSuccess) { fprintf(stderr, "cudaMalloc failed!"); goto Error; } // Copy input vectors from host memory to GPU buffers. cudaStatus = cudaMemcpy(dev_a, a, size * sizeof(int), cudaMemcpyHostToDevice); if (cudaStatus != cudaSuccess) { fprintf(stderr, "cudaMemcpy failed!"); goto Error; } cudaStatus = cudaMemcpy(dev_b, b, size * sizeof(int), cudaMemcpyHostToDevice); if (cudaStatus != cudaSuccess) { fprintf(stderr, "cudaMemcpy failed!"); goto Error; } // Launch a kernel on the GPU with one thread for each element. addKernel<<<4,256>>>(dev_c, dev_a, dev_b); // Check for any errors launching the kernel cudaStatus = cudaGetLastError(); if (cudaStatus != cudaSuccess) { fprintf(stderr, "addKernel launch failed: %s\n", cudaGetErrorString(cudaStatus)); goto Error; } // cudaDeviceSynchronize waits for the kernel to finish, and returns // any errors encountered during the launch. cudaStatus = cudaDeviceSynchronize(); if (cudaStatus != cudaSuccess) { fprintf(stderr, "cudaDeviceSynchronize returned error code %d after launching addKernel!\n", cudaStatus); goto Error; } // Copy output vector from GPU buffer to host memory. cudaStatus = cudaMemcpy(c, dev_c, size * sizeof(int), cudaMemcpyDeviceToHost); if (cudaStatus != cudaSuccess) { fprintf(stderr, "cudaMemcpy failed!"); goto Error; } Error: cudaFree(dev_c); cudaFree(dev_a); cudaFree(dev_b); return cudaStatus; }这里单个块内的线程数为256,块的个数为4。 块内线程数的选择有一个warp的概念,一个block内的线程同时也被打包成多个warp,一个warp由32各线程组成,所以为了让资源不浪费,即warp内的线程数被充分利用,线程数经常设置为32的整数倍。
