许多代码示例将用于说明编写可扩展并行程序的关键概念。为此,我们需要一种支持大规模并行性和异构计算的简单语言,我们选择了CUDA C作为我们的代码示例和练习。CUDA C以最少的新语法和接口扩展了流行的C编程语言,让程序员针对包含CPU核心和大规模并行GPU的异构计算系统。顾名思义,CUDA C建立在NVIDIA的CUDA平台上。CUDA是目前最成熟的大规模并行计算框架。它广泛用于高性能计算行业,在最常见的操作系统上提供复杂的工具,如编译器、调试器和分析器。
重要的一点:虽然我们的示例将主要使用CUDA C的简单性和普遍性,但CUDA平台支持许多语言和应用程序编程接口(API),包括C++、Python、Fortran、OpenCL、OpenACC、OpenMP等。CUDA实际上是一个支持一组组织和表达大规模并行计算概念的架构。我们教的正是这些概念。为了使用其他语言(C++、FORTRAN、Python、OpenCL等)工作的开发人员的利益,我们提供了附录,展示了如何将这些概念应用于这些语言。
当现代软件应用程序运行缓慢时,问题通常是有太多的数据需要处理。消费者应用程序以数百万到万亿的像素操作图像或视频。科学应用使用数十亿个网格细胞模拟fuid动力学。分子动力学应用必须模拟数千到数百万个原子之间的相互作用。航空公司调度处理数千个航班、机组人员和机场登机口。重要的是,这些像素、粒子、细胞、相互作用、飞行等大多可以在很大程度上独立处理。将彩色像素转换为灰度只需要该像素的数据。模糊图像将每个像素的颜色与附近像素的颜色平均,只需要该小像素邻域的数据。即使是看似全局的操作,例如找到图像中所有像素的平均亮度,也可以分解为许多可以独立执行的较小计算。这种独立评估是数据并行性的基础:(重新)围绕数据组织计算,以便我们可以并行执行由此产生的独立计算,以更快地完成整体工作,通常更快。
任务并行性与数据并行性
数据并行性并不是并行编程中使用的唯一类型。任务并行性也被广泛用于并行编程。任务并行性通常通过应用程序的任务分解来暴露。例如,一个简单的应用程序可能需要进行向量加法和矩阵-向量乘法。每一个都是一项任务。如果这两项任务可以独立完成,则存在任务并行性。1/O和数据传输也是任务的常见来源。
在大型应用程序中,通常有更多的独立任务,因此任务并行性更大。例如,在分子动力学模拟器中,自然任务列表包括振动力、旋转力、非键合力的邻接识别、非键合力、速度和位置,以及基于速度和位置的其他物理属性。
一般来说,数据并行性是并行程序可扩展性的主要来源。有了大型数据集,人们通常可以找到丰富的数据并行性,以便能够利用大规模并行处理器,并允许应用程序性能随着拥有更多执行资源的每一代硬件而增长。然而,任务并行性也可以在实现绩效目标方面发挥重要作用。稍后,当我们引入流时,我们将介绍任务并行性。
在接下来的章节中,我们将使用图像处理作为运行示例的来源。让我们用上面提到的彩色到灰度的转换示例来说明数据并行性的概念。图2.1显示由许多像素组成的彩色图像(左侧),每个像素包含红色、绿色和蓝色分数值(r、g、b从0(黑色)到1(全强度)不等)。
RGB彩色图像表示
在RGB表示中,图像中的每个像素都存储为(r,g,b)值的元组。图像行的格式是(rg b)(rg b)…(rg b),如以下概念图片所示。每个元组指定红色(R)、绿色(G)和蓝色(B)的混合物。也就是说,对于每个像素,r、g和b值表示渲染像素时红色、绿色和蓝色光源的强度(0为暗,1为全强度)。
这三种颜色的实际允许混合物因行业指定的颜色空间而异。在这里,AdobeRGB色彩空间中三种颜色的有效组合显示为三角形的内部。每种混合物的垂直坐标(y值)和水平坐标(x值)显示像素强度的分数应该是G和R。应分配给B的像素强度的剩余部分(1-y-x)。为了渲染图像,每个像素的r、g、b值用于计算像素的总强度(亮度)以及混合系数(x、y、1-y-x)。
转换彩色图像(图2.1的左侧)到灰度(右侧),我们通过应用以下加权和公式计算每个像素的亮度值L:
如果我们认为输入是组织为RGB值数组I的图像,输出是亮度值的相应数组O,我们得到图中所示的简单计算结构。2.2.例如,O[0]是通过根据上述公式计算I[0]中RGB值的加权和生成的;O[1]通过计算I[1]中RGB值的加权和,O[2]通过计算I[2]中RGB值的加权和,以此等。这些每像素计算都不相互依赖;所有这些都可以独立执行。显然,颜色到灰度的转换表现出丰富的数据并行性。当然,完整应用程序中的数据并行性可能更复杂,这本书的大部分内容都致力于教授查找和利用数据并行性所需的“并行思维”。