在过去的几十年里,已经提出了许多并行编程语言和模型[Mattson,2004]。使用最广泛的是用于可扩展集群计算的消息传递接口(MPI)[MPI 2009]和用于共享内存多处理器系统的OpenMP[Open 2005]。两者都已成为主要计算机供应商支持的标准化编程接口。OpenMP实现由编译器和运行时组成。程序员指定关于OpenMP编译器循环的指令(命令)和pragmas(提示)。有了这些指令和pragmas,OpenMP编译器可以生成并行代码。运行时系统通过管理并行线程和资源来支持并行代码的执行。OpenMP最初是为CPU执行而设计的。最近,一个名为OpenACC的变体(见章节:与OpenACC并行编程)已被提出并得到多个计算机供应商的支持,用于对异构计算系统进行编程。
OpenACC的主要优点是它提供编译器自动化和运行时支持,以从程序员那里抽象出许多并行编程细节。这种自动化和抽象可以帮助应用程序代码在不同供应商以及同一供应商的不同代系统之间更便携。我们可以将此属性称为“性能可移植性”,这就是为什么我们在第19章中教授OpenACC编程,使用OpenACC进行并行编程。然而,OpenACC中的有效编程仍然需要程序员了解所涉及的所有详细的并行编程概念。由于CUDA让程序员明确控制这些并行编程细节,即使对于想要使用OpenMP和OpenACC作为主要编程界面的人来说,它也是一个很好的学习工具。此外,根据我们的经验,OpenACC编译器仍在不断发展和改进。许多程序员可能需要对OpenACC编译器不足的部分使用CUDA风格的接口。
MPI是一个集群中的计算节点不共享内存的模型[MPI 2009]。所有数据共享和交互必须通过显式消息传递来完成。MPl在高性能计算(HPC)方面取得了成功。用MPI编写的应用程序在拥有超过100,000个节点的集群计算系统上成功运行。今天,许多HPC集群使用异构CPU/GPU节点。虽然CUDA是每个节点的有效接口,但大多数应用程序开发人员需要使用MPI在集群级别进行编程。因此,HPC中的并行程序员必须了解如何进行联合MPI/CUDA编程,该编程载于第18章,编程异构计算集群。
然而,由于缺乏跨计算节点的共享内存,将应用程序移植到MPI所需的工作量可能相当高。程序员需要执行域分解,将输入和输出数据分区为集群节点。基于域分解,程序员还需要调用消息发送和接收函数来管理节点之间的数据交换。另一方面,CUDA为GPU中的并行执行提供共享内存,以解决这一困难。至于CPU和GPU通信,CUDA以前在CPU和GPU之间提供了非常有限的共享内存能力。程序员需要以类似于“片面”消息传递的方式管理CPU和GPU之间的数据传输。异构计算系统中对全局地址空间和自动数据传输的新运行时支持现已推出,例如GMAC [GCN 2010]。有了这种支持,CUDA程序员可以声明CPU和GPu之间共享的变量和数据结构。运行时硬件和软件通过根据需要代表程序员自动执行优化的数据传输操作,透明地保持一致性。这种支持大大降低了数据传输与计算和1/O活动重叠所涉及的编程复杂性。正如第20章稍后关于CUDA和GPU计算的讨论的那样,Pascal架构支持统一的全局地址空间和内存。
2009年,包括苹果、英特尔、AMD/ATL、NVIDIA在内的几个主要行业参与者联合开发了一种名为开放计算语言(OpenCL)的标准化编程模型[Khronos 2009]。与CUDA类似,OpenCL编程模型定义了语言扩展和运行时API,以允许程序员在大规模并行处理器中管理并行和数据交付。与CUDA相比,OpenCL更多地依赖API,而不是语言扩展。这允许供应商快速调整他们现有的编译器和工具来处理OpenCL程序。OpenCL是一个标准化的编程模型,在OpenCL中开发的应用程序可以在所有支持OpenCL语言扩展和API的处理器上正常运行,而无需修改。然而,人们可能需要修改应用程序,以便为新的prOceSSor实现高性能。
那些熟悉OpenCL和CUDA的人知道,OpenCL的关键概念和功能与CUDA的关键概念和功能之间存在显著的相似性。也就是说,CUDA程序员可以毫不费力地学习OpenCL编程。更重要的是,几乎所有使用CUDA学习的技术都可以轻松应用于OpenCL编程。因此,我们在附录A中介绍了OpenCL,并解释了如何将本书中的关键概念应用于OpenCL编程。