【随手一记】cuda-c-programming-guide学习笔记(一)

Introduction

1.CPU与GPU之间的差异。

2.GPU适合解决并行计算的原因:①同一个程序中的数据并行执行 ②计算密度高 ③计算与存储操作的比率

3.线程并行处理:大量数据计算可以使用并行编程加速。(将计算的数据、图像等映射到线程)

4.并行的挑战:开发能够透明地扩展其并行性的应用软件,以利用越来越多的处理器内核,就像3D图形应用程序透明地将其并行性扩展到核心数量众多的许多GPU一样。

5.Cuda核心:(三个关键抽象)——线程组的层次结构,共享内存和屏障同步。这些抽象提供了细粒度的数据并行和线程并行,嵌套在粗粒度数据并行和任务并行中。每个线程块都可以在GPU内的任何可用多处理器上按任意顺序同时或按顺序进行调度,以便编译的CUDA程序可以在任何数量的多处理器上执行。

总结:GPU是围绕一系列流式多处理器(SM=streaming multiprocessors)构建的。多线程程序被划分为彼此独立执行的线程块,因此具有更多多处理器的GPU将以比具有更少多处理器的GPU更少的时间自动执行程序。


Programing Model

1.kernel核函数:CUDA C通过允许程序员定义C函数(称为内核)来扩展C,C函数在被调用时被N个不同的CUDA线程并行执行N次,而不是像常规C函数那样只执行一次。

2.kernel声明:使用__global__声明,使用新的<<< ... >>>执行配置语法指定执行该内核的CUDA线程的数量。 执行内核的每个线程都被赋予一个唯一的线程ID,可通过内置的threadIdx变量在内核中访问。

例子:

// Kernel definition
__global__ void VecAdd(float* A, float* B, float* C)
{
    int i = threadIdx.x;
    C[i] = A[i] + B[i];
}

int main()
{
    ...
    // Kernel invocation with N threads
    VecAdd<<<1, N>>>(A, B, C);
    ...
}
这里,执行VecAdd()的N个线程中的每一个执行一对成对的加法。

3.线程的层次结构:threadIdx是一个3分量向量,因此可以使用一维,二维或三维索引来标识线程。线程的索引和它的线程ID以直接的方式相互关联:

对于一维块,它们是相同的;

 对于二维块(Dx,Dy),索引(x,y)的线程的线程ID为(x + y Dx); 

对于三维块(Dx,Dy,Dz),索引(x,y,z)的线程的线程ID为(x + y Dx + z Dx Dy)。

例子:下面的代码添加两个大小为NxN的矩阵A和B,并将结果存储到矩阵C中:

// Kernel definition
__global__ void MatAdd(float A[N][N], float B[N][N],
                       float C[N][N])
{
    int i = threadIdx.x;
    int j = threadIdx.y;
    C[i][j] = A[i][j] + B[i][j];
}

int main()
{
    ...
    // Kernel invocation with one block of N * N * 1 threads
    int numBlocks = 1;
    dim3 threadsPerBlock(N, N);
    MatAdd<<<numBlocks, threadsPerBlock>>>(A, B, C);
    ...
}


4.块VS线程:每个块的线程数是有限制的,因为一个块的所有线程都应该驻留在同一个处理器内核上,并且必须共享该内核的有限内存资源。 在当前的GPU上,一个线程块最多可以包含1024个线程。但是,内核可以由多个形状相同的线程块执行,因此线程的总数等于每块的线程数乘以块的数量。

每个块的线程数和<<< ... >>>语法中指定的每个网格的块数可以是int类型或dim3类型。 可以像上面的例子那样指定二维块或网格。
网格中的每个块都可以通过内核中通过内置blockIdx变量在内核中访问的一维,二维或三维索引来标识。 线程块的维度可以通过内置的blockDim变量在内核中访问。

扩展前面的MatAdd()示例以处理多个块,代码变为如下所示:

__global__ void MatAdd(float A[N][N], float B[N][N],
float C[N][N])
{
    int i = blockIdx.x * blockDim.x + threadIdx.x;
    int j = blockIdx.y * blockDim.y + threadIdx.y;
    if (i < N && j < N)
        C[i][j] = A[i][j] + B[i][j];
}

int main()
{
    ...
    // Kernel invocation
    dim3 threadsPerBlock(16, 16);
    dim3 numBlocks(N / threadsPerBlock.x, N / threadsPerBlock.y);
    MatAdd<<<numBlocks, threadsPerBlock>>>(A, B, C);
    ...
}

5.内存层次结构:所有线程都可以访问相同的全局内存。

6.异构编程:CUDA编程模型假定CUDA线程在物理上分离的设备上执行,该设备作为运行C程序的主机的协处理器运行。例如,当内核在GPU上执行时,C程序的其余部分在CPU上执行时就是这种情况。
CUDA编程模型还假定主机和设备都在DRAM中分别维护其自己的独立存储空间,分别称为主机存储器和设备存储器。因此,程序通过调用CUDA运行时来管理内核可见的全局,常量和纹理内存空间(在编程接口中描述)。这包括设备内存分配和重新分配以及主机和设备内存之间的数据传输。

统一内存提供托管内存以桥接主机和设备内存空间。托管内存可通过系统中的所有CPU和GPU作为具有公共地址空间的单一连贯内存映像访问。这种功能可以实现对设备内存的超额订购,并且可以通过消除在主机和设备上显式镜像数据的需求,大大简化移植应用程序的任务。

猜你喜欢

转载自blog.csdn.net/asdu8ko8878/article/details/80421291