它涉及多个处理器或计算机的使用, 以高吞吐量和高效率完成一个复杂的任务。HPC不仅可以认为是一个计算架构,还可以认为是包括硬件系统,软件工具,编程平台及并行编程范例的一组元素列表。
从纯粹的计算视角来看,并行计算可以被定义为计算的一种形式。在这种形式下,计算机可以同时进行许多运算。计算原则是一个大问题往往可被划分为很多可以同时 解决的小问题。
从程序员角度来说,如何将并发计算映射到计算机上。并行计算可以被定义为同时使用许多计算资源(核心或计算机)来执行并发计算,一个大问题可以被分解为多个小问题,然后在不同的计算资源上并行处理这些小问题。
其中通常涉及两个计算技术领域 :计算机架构(硬件)、并行程序设计(软件)。
计算机架构关注的是结构级别上支持并行性,而并行程关注充分使用计算机架构的计算能力来并发的解决问题。
大多数现代处理器用到Harvard Architecture架构。它主要由三部分完成:
内存(指令内存和数据内存);
中央处理单元(控制单元和算术逻辑单元);
输入/输出接口;
现在,芯片设计的趋势是将多个核心继成到一个单一的处理器上,以在体系结构级别支持并行性,这种形式通常被称为多核处理器。因此,并行程序设计可以看作是将一个问题的计算分配给可用的核心以实现并行的过程。
有两种方法可用区分两个计算单元之间的关系:有些是有执行顺序的,所以必须串行执行;其他的没有执行次序的约束,则可用并发执行。
从程序员的角度来看,一个程序应包含两个基本的组成部分:指令和数据。当一个计算问题被划分成许多小的计算单元后,每个计算单元都是一个任务。在一个任务中,单独的指令负责处理输入和调用一个函数并产生输出。当一个指令处理前一个指令产生的数据时,就有了数据相关性的概念。
如果一个任务处理的是另一个任务的输出,那么它们就是相关的,否则就是独立的。
在并行算法的实现中,分析数据的相关性是最基本的内容,因为相关性是限制并行性的一个主要因素。
在大多数情况下,具有依赖关系的任务之间的独立的关系链为并行化提供了很好的机会。
在应用程序中有两种基本的并行类型:
任务并行;
数据并行;
当许多任务或函数可用独立地、大规模地并行执行时,这就是任务并行。任务并行地重点在于利用多核系统对任务进行分配。
当同时处理许多数据时,这就是数据并行。数据并行的重点在于利用多核系统对数据进行分配。
CUDA编程非常适合解决数据并行的计算问题。
数据并行程序设计的第一步是把数据依据线程划分,以便每个线程处理一部分的数据。通常,有两种方式对数据进行划分:块划分(block partition)和周期划分(cyclic partition)。在块划分中,一组连续的数据被分到一个块内。每个数据块以任意次序被安排给一个线程,线程通常在同一时间只处理一个数据块。在周期划分中,更少的数据被分到一个块内。相邻的线程处理相邻的数据块,每个线程可用来处理多个数据块。为一个待处理的线程选择一个新的块,就意味着要跳过和现有线程一样多的数据块。
通常,数据是在一维空间中存储的。即便是多维逻辑数据,仍然要被映射到一维物理地址空间中。如何在线程中分配数据不仅与数据的物理存储方式密切相关,并且与每个线程的执行次序也有很大关系。组织线程的方式对程序的性能有很大的影响。
Flynn's分类:
数据与指令进入CPU的方式。SISD/SIMD/MISD/MIMD
SISD: 一种串行架构。计算机只有一个核心。在任何时间点上只有一个指令流在处理一个数据流。
SIMD: 一种并行架构。计算机上有多个核心。在CPU上编写代码时,程序员可继续按串行逻辑思考但对并行数据实现并行加速,而其他细节有编译器来负责。
MISD: 每个核心通过使用多个指令流处理同一个数据流。
MIMD:一种并行架构。多个核心使用多个指令流来异步处理多个数据流,从而实现空间上的并行性。
为实现并行计算,架构层次取得的进展:
降低延迟;
提高带宽;
提高吞吐量;
延迟是一个操作从开始到完成所需要的时间,常用微秒us来表示。用来衡量完成一次操作的时间。
带宽是单位时间内可处理的数据量,通常表示为MB/s或者GB/s。
吞吐量是单位时间成功处理的运算数量,通常表示为gflops(每秒十亿次浮点运算数量),Tfloaps(每秒万亿次浮点运算数量)。用来衡量在给定的单位时间内处理的操作量。
延迟用来衡量完成一次操作的时间,而吞吐量用来衡量在给定的单位时间内处理的操作量。
计算机架构也能根据内存组织方式进行下一步的划分;
分布式的多节点系统;
共享内存的多处理器系统;
在多节点系统中,大型计算机引擎是由许多网络连接的处理器构成的。每个处理器有自己的本地内存,而且处理器之间可用通过网络进行通信。一个典型的分布式内存的多节点系统,即集群。
多处理器架构的大小通常是从双处理器到几十或者几百处理器之间。这些处理器要么是与同一物理内存相关联,要么共用一个低延迟的链路(PCI-E)。
众核通常是指有很多核心的多核架构。
GPU代表一种众核架构,几乎包括了前文描述的所有并行架构:多线程、MIMD/SIMD/,指令级并行。Nvidia公司称其架构为SIMT(Single Instruction Multiple Thread).。
GPU核心与CPU核心
CPU核心比较重,用来处理非常复杂的控制逻辑,以优化串行程序执行;
GPU核心比较轻,用于优化简单控制逻辑的数据并行任务,注重并行程序的吞吐量;
CPU与GPU是两个独立的处理器,他们通过单个计算节点中PCI-Express总线相连。GPU是离散的设备从同构系统到异构系统的转变是高性能计算史上的一个里程碑。同构计算使用的是同一个架构下的一个或多个处理器来执行一个应用。而异构计算则使用一个处理器架构来执行一个应用,为任务选择合适它的架构,使其最终对性能有所改进。
一个典型的异构计算节点包括两个多核CPU插槽和两个或更多个众核GPU。GPU不是一个独立运行的平台而是CPU的协处理器。因此,GPU必须通过PCIe总线与基于CPU的主机相连来进行操作。CPU在的位置被称作主机端,而GPU所在的位置被称作设备端。
一个异构应用包括两个部分:
主机代码;
设备代码;
主机代码在CPU上执行,设备代码在GPU上执行。异构平台上执行的应用通常由CPU初始化。在设备端加载计算密集型任务之前,CPU代码负责管理环境、代码和数据。
在GPU上执行数据并行工作,而在CPU上执行串行和任务并行工作。
2010年,Nvidia公司发布的Feimi架构是世界上第一款完整的GPU计算架构。
以下是两个描述GPU容量的重要特征:
CUDA核心数目;
内存大小;
相应的两个评价GPU性能指标:
峰值计算性能;
内存带宽;
峰值带宽是用来评估计算容量的一个指标,通常定义为每秒能处理的单精度或双精度浮点运算的数量,单位GFlops。
内存带宽是从内存读取和写入数据的比率。内存带宽通常用GB/s表示。
CPU适合处理控制密集型任务,GPU计算适合处理包括数据并行的计算密集型任务。
GPU与CPU结合后,能有效提高大规模计算问题的处理速度与性能。CPU针对动态工作负载进行了优化,这些动态工作负载是由短序列的计算操作和不可预测的控制流标记的。
而GPU在其他领域内的目的是:处理由计算任务主导的且带有简单控制流的工作负载。
可用从两个方面来区分CPU与GPU应用的范围:
并行级;
数据规模;
如果一个问题有较小的数据规模、复杂的控制逻辑和/或很少的并行性,那么最好选择CPU处理该问题。
如果该问题包括较大规模的待处理数据并行并表现出大量的数据并行性,那么使用GPU是最好的选择。
在CPU上执行串行部分或任务并行部分,在GPU上执行数据密集型并行部分。
CPU线程与GPU线程
CPU上的线程通常是重量级实体。操作系统交替线程使用启用或关闭CPU执行通道以提供多线程处理功能。上下文的切换缓慢且开销大。
GPU上的线程是高度轻量级的。在一个典型的系统中会有成千上万的线程排队等待工作。如果GPU必须等待一组线程执行结束,那么它只要调用另一组线程执行其他的任务即可。
CPU的核被设计用来尽可能减少一个或两个线程运行时间的延迟,而GPU的核实用来处理大量并发的、轻量级的线程,以最大限度地提高吞吐量。
CUDA是一种通用的并行计算平台和编程模型。CUDA平台可以通过CUDA加速库、编译器指令、应用程序接口以及行业标准程序语言的扩展来使用。
CUDA C是标准ANSI C语言的一个扩展,它带有少数语言扩展功能使异构编程成为可能,同时通过API来管理设备、内存和其他任务。
CUDA提供两层API来管理GPU设备和组织线程:
CUDA驱动API;
CUDA运行时API;
驱动API是一种低级API,它相对来说较难编程,但是它对GPU设备使用上提供了更多的控制。
运行时API是一个高级API,它在驱动API的上层实现。每个运行时API函数都被分解为更多传给驱动API的基本运算。
这两种API性能上没有明显的差异。但是两种API是相互排斥的,必须使用二者之一。
CUDA NVCC编译:
在编译过程中将设备代码从主机代码中分离出来。主机代码是标准的C代码,使用C编译器进行编译。设备代码,也就是核函数,使用扩展的带有标记数据并行函数关键字的CUDA C语言编写的。设备代码通过nvcc进行编译。在链接阶段,在内核函数调用和显示GPU设备操作中添加CUDA运行时库。
CUDA nvcc编译器是以广泛使用的LLVM开源编译系统为基础的。
强化CUDA性能的最简单方法是使用CUDA工具包。CUDA工具包包括编译器、数学库、以及调试和优化应用程序性能的工具。同时提供了代码样例、编程指南、用户手册、API参考文档。
写一个CUDA C程序,你需要以下几个步骤:
用专用的扩展名.cu来创建一个源文件。
使用CUDA nvcc编译器来编译程序。
从命令行运行可执行文件。
修饰符__global__告诉编译器这个函数将会从CPU中调用,然后在GPU上执行。
三重尖括号意味着从主线程到设备端代码的调用。一个内核函数通过一组线程来执行,所有的线程执行相同的代码。三重尖括号里面的参数是执行配置,用来说明使用多少线程来执行内核函数。
函数cudaDeviceReset()用来显式地释放和清空当前进程中与设备有关的所有资源。
数据局部性在并行编程中是一个非常重要的概念。数据局部性是指数据重用,以降低内存访问的延迟。数据局部性有两种基本类型。时间局部性是指在相对较短的时间段内数据/资源的重用。空间局部性是指在相对接近的存储空间内数据元素的重用。
现代的CPU架构使用大容量缓存来优化具有良好空间局部性和时间局部性的应用程序。设计高效利用CPU缓存的算法是程序员的工作。
CUDA中有内存层次和线程层次的概念:
内存层次结构;
线程层次结构;
共享内存可以视为一个被软件管理的高速缓存,通过为主内存节省带宽来大幅提高运行速度。有了共享内存,你可以直接控制代码的数据局部性。
GPU处理这个内核函数,然后通过启动成千上万线程来实现并行化,所有的线程都执行相同的计算。CUDA编程模型提供了一个层次化组织线程的方法,它直接影响到线程在GPU上的执行顺序。
CUDA核中有三个关键抽象:
线程组的层次结构;
内存的层次结构;
障碍同步;
这三个抽象是最小的一组语言扩展。
CUDA开发环境:
Nvidia Nsight集成开发环境;
CUDA-GDB命令行调试器;
用于性能分析的可视化和命令行分析器;
CUDA-MEMCHECK内存分析器;
GPU设备管理工具;
在GPU上执行数据并行工作,在CPU上执行串行和任务并行工作。