前面几章主要是对参考书目的内容进行一个概括。本章将根据参考书目的内容对所学到的所有函数进行一个整理和总结,以便复习和参考。英伟达的官方网站包含了所有的CUDA函数,可参考https://developer.download.nvidia.cn/compute/DevZone/docs/html/C/doc/html/index.html

这部分是基于原子操作章节进行的高级操作介绍,即实现锁定数据结构。

原子操作只能确保每个线程在完成读取-修改-写入的序列之前,将不会有其他的线程读取或者写入目标内存。然而,原子操作并不能确保这些线程将按照何种顺序执行。例如当有三个线程都执行加法运算时,加法运行的执行顺序可以为(A + B) + C,也可以为A + (B + C)。这对于整数来说是可以接受的,因为中间结果可能被截断,因此(A + B) + C通常并不等于A + (B + c)。因此,浮点数值上的原子数学运算是否有用就值得怀疑🤔。因此在早期的硬件中,浮点数学运算并不是优先支持的功能。

然而,如果可以容忍在计算结果中存在某种程度的不确定性,那么仍然可以完全在GPU上实现归约运算。我们首先需要通过某种方式来绕开原子浮点数学运算。在这个解决方案中仍将使用原子操作,但不是用于算数本身。

本章将介绍如何分配和使用零拷贝内存(Zero-Copy Memory),如何在同一个应用程序中使用多个GPU,以及如何分配和使用可移动的固定内存(Portable Pinned Memory)。 零拷贝主机内存 上一章介绍了固定内存(页锁定内存),这种新型的主机内存能够确保不会交换出物理内存。我们通过cudaHostAlloc()来分配这种内存,并且传递参数cudaHostAllocDefault来获得默认的固定内存。本章会介绍在分配固定内存时可以使用其他参数值。除了cudaHostAllocDefault外,还可以传递的标志之一是cudaHostAllocMapped。通过cudaHostAllocMapped分配的主机内存也是固...

在并行环境中,任务可以是任意操作。例如,应用程序可以执行两个任务:其中一个线程重绘程序的GUI,而另一个线程通过网络下载更新包。这些任务并行执行,彼此之间没有任何共同的地方。虽然GPU上的任务并行性并不像CPU上的任务并行性那样灵活,但仍然可以进一步提高程序在GPU上的运行速度。本章将介绍CUDA流,以及如何通过流在GPU上同时执行多个任务。 页锁定(Page-Locked)主机内存 CUDA提供了自己独有的机制来分配主机内存:cudaHostAlloc()。malloc()分配的内存与cudaHostAlloc()分配的内存之间存在一个重要差异。C库函数malloc()将分配标准的、可分页的(Pagable)主机内存,...

在某些情况中,对于单线程应用程序来说非常简单的任务,在大规模并行架构上实现时会变成一个复杂的问题。在本章中,我们将举例其中一些情况,并在这些情况中安全地完成传统单线程应用程序中的简单任务。 原子操作简介 在编写传统的单线程应用程序时,程序员通常不需要使用原子操作。下面会介绍一下原子操作是什么,以及为什么在多线程程序中需要使用它们。我们先分析C或者C++的递增运算符: x++; 在这个操作中包含了三个步骤: 读取x中的值。 将步骤1中读到的值增加1。 将递增后的结果写回到x。 有时候,这个过程也称为读取-修改-写入操作。 当两个线程都需要对x的值进行递增时,假设x的初始值为7,理想情况下,两个线程按顺序对x进行递增,第一个线程完成三个...

本章将学习如何分配和使用纹理内存(texture memory)。和常量内存一样,纹理内存是另一种类型的只读内存,在特定的访问模式中,纹理内存同样能够提升性能并减少内存流量。虽然纹理内存最初是针对传统的图形处理应用程序而设计的,但在某些GPU计算应用程序中同样非常有用。 与常量内存相似的是,纹理内存同样缓存在芯片上,因此在某些情况中,它能够减少对内存请求并提供更高效的内存带宽。纹理缓存是专门为那些在内存访问模式中存在大量空间局部性(spatial locality)的图形应用程序而设计的。在某个计算应用程序中,这意味着一个线程读取的位置可能与邻近线程读取的位置“非常接近”。 热传导模拟 本章用一个简单的热传导模拟的模型来介绍如何使用...

这一章会介绍如何在CUDA C中使用常量内存、了解常量内存的性能特性以及学习如何使用CUDA事件来测量应用程序的性能。 常量内存 到目前为止,我们知道CUDA C程序中可以使用全局内存和共享内存。但是,CUDA C还支持另一种类型的内存,即常量内存。常量内存用于保存在核函数执行期间不会发生变化的数据。在某些情况下,用常量内存来替换全局内存能有效地减少内存带宽。 在GPU上实现光线跟踪 我们给出一个简单的光线跟踪应用程序示例来学习。由于OpenGL和DirectX等API都不是专门为了实现光线跟踪而设计的,因此我们必须使用CUDA C来实现基本的光线跟踪器。本示例构造的光线跟踪器非常简单,旨在学习常量内存的使用上(并不能通过示例代码来...

这一章将介绍线程块以及线程之间的通信机制和同步机制。 在GPU启动并行代码的实现方法是告诉CUDA运行时启动核函数的多个并行副本。我们将这些并行副本称为线程块(Block)。 CUDA运行时将把这些线程块分解为多个线程。当需要启动多个并行线程块时,只需将尖括号中的第一个参数由1改为想要启动的线程块数量。 在尖括号中,第二个参数表示CUDA运行时在每个线程块中创建的线程数量。假设尖括号中的变量为«<N, M»>总共启动的线程数量可以按照以下公式计算: $$ N个线程块 * M个线程/线程块 = N*M个并行线程 $$ 使用线程实现GPU上的矢量求和 在之前的代码中,我们才去的时调用N个线程块,每个线程块对应一个线程add<<<N,...

这一部分将介绍CUDA的并行编程方式 矢量求和运算 假设有两组数据,我们需要将这两组数据中对应的元素两两相加,并将结果保存在第三个数组中。 基于CPU的矢量求和 首先,下面的代码是通过传统的C代码来实现这个求和运算 #include "../common/book.h" #define N 10 void add(int *a, int *b, int *c){ int tid = 0; //这是第0个CPU,因此索引从0开始 while(tid < N){ c[tid] = a[tid] + b[tid]; tid += 1; //由于只有一个CPU,因此每次递增1 } } int main(){ int a[N], b[N],...

参考书目: GPU高性能编程CUDA实战 书目网页链接: https://hpc.pku.edu.cn/docs/20170829223652566150.pdf 该博客参考于上述书籍,虽然书有一点老,但是作为初学者而言仍然能学到很多东西。 本书所包含的代码都在下面的连接中,可以下载来学习: https://developer.nvidia.com/cuda-example CUDA C简介 首先来看一个CUDA C的示例: #include "../common/book.h" int main(){ prinf("Hello World!\n"); return 0; } 这个示例只是为了说明,CUDA C与熟悉的标准C在很大程...