并行计算概述
在计算机领域,并行性描述了将一个复杂问题分解为多个能够同时处理的子问题的能力。为了实现并行计算,我们需要在物理上能够支持并行处理的硬件设备。典型例子是多核CPU,每个核心都能同时执行算术或逻辑运算
在GPU并行计算中,通常会实现两种主要类型的并行计算:
- 任务并行:将问题分解为多个可以同时执行的任务,任务之间彼此独立
- 数据并行:在同一个任务中,各个部分的数据可以同时执行,多个处理单元并行处理数据的不同片段
举例说明
可以通过一个简单的例子来理解:假设一个农场主雇佣工人来摘苹果
- 工人相当于硬件中的并行处理单元
- 树代表需要执行的任务
- 苹果则是需要处理的数据
串行任务处理
如果农场主只雇佣一个工人,该工人需要自己带着梯子摘完所有树上的苹果。这类似于一个处理单元处理完所有任务的数据
数据并行
农场主雇佣了多个工人,他们一起摘完一棵树上的苹果。也就是说,多个处理单元并行完成了同一个任务中的数据处理,每个工人负责不同的苹果,从而更快地完成任务
任务并行
每棵树分配给一个工人,每个工人独自负责一棵树。虽然每棵树的任务(摘苹果)是串行的,但不同树的任务是并行执行的
通过这些例子可以看到,数据并行和任务并行的区别在于,前者在同一任务内并行处理数据,而后者是在任务层次上实现并行
并行计算中的任务和数据分解
对于复杂问题,影响并行计算的因素有很多。通常,我们通过任务分解和数据分解来实现并行计算:
-
任务分解:把算法分解成多个小任务,每个任务可以独立执行,而不关心具体数据。例如,在并行计算中计算图像的不同区域,或者执行多个独立的物理模拟任务
-
数据分解:将大量数据划分为多个小块,每个数据块能够独立并行处理。这种方式特别适合处理大量相同类型的数据,如矩阵运算、图像处理等
任务的分解通常基于任务之间的依赖关系,一个任务只有在没有依赖其他任务时才可以执行。这类似于数据结构中的有向无环图(DAG)。两个没有依赖关系的任务可以并行执行
对于许多科学计算和工程应用,数据分解通常基于输出数据。例如:
- 在图像处理的卷积操作中,对图像的一个滑动窗口进行滤波操作可以生成一个输出像素
- 矩阵相乘中,一个输入矩阵的第i行乘以第二个矩阵的第j列,得到的结果就是输出矩阵第i行第j列的值
这种基于输出的分解方法特别适合输入和输出数据存在一对一或多对一关系的场景。然而,对于输入和输出存在一对多关系的情况,如计算图像的直方图,我们可以让每个线程计算部分输出,并通过同步或原子操作来最终得到结果。典型应用如OpenCL中求最小值的kernel函数
并行编程与硬件
并行问题的分解方式通常与算法相关,并且需要考虑所使用的硬件平台。例如,AMD和Nvidia的GPU平台在并行编程中有不同的实现,针对不同类型的任务有不同的优化策略
- 多核CPU 通常适合任务并行,因为每个核心可以独立执行复杂的任务
- GPU 更适合数据并行编程,由于GPU包含大量计算单元,能够同时处理大量数据片段
现代GPU由许多独立的运算核组成。通常,我们在GPU上执行任务时,将任务的数据分配给各个独立的运算核来处理。这些核能够执行SIMD(单指令多数据)操作,显著加速数据密集型计算
循环展开与并行化
在GPU上,通常我们通过循环展开(Loop strip mining)技术,将串行代码转化为并行代码。循环展开的基本思想是将一个大的循环分解成多个可以并行执行的“块”。每个线程处理一块数据,从而提高硬件利用率
假设我们在CPU上执行向量加法,代码如下:
for(i =0; i < n; i++)
{
C[i]= A[i]+ B[i];
}
而在GPU上,我们可以为每个数据点创建一个线程,每个线程负责一个加法操作:
__kernel void VectorAdd(__global const float* a, __global const float* b, __global float* c, int n)
{
int i = get_global_id(0);
c[i] = a[i] + b[i];
}
通过这种方式,GPU可以高效并行处理每个数据片段。现代GPU上的线程开销较小,因此能够处理大量线程而不显著影响性能
SPMD(单程序多数据)模型
GPU程序通常被称为Kernel程序,它采用SPMD(Single Program, Multiple Data)的编程模型。在这种模型中,同一个程序的多个实例会在不同的数据上并行执行
- 在分布式系统中,使用MPI(消息传递接口)实现SPMD,每个计算节点处理不同的数据块。
- 在共享内存并行系统中,可以使用POSIX线程来实现SPMD,每个线程负责不同的数据
- 在GPU中,SPMD由Kernel程序执行。由于GPU上的线程是轻量级的,其创建和调度开销较小,可以将循环完全展开,每个线程处理一个数据片段
SPMD模型可以很好地利用GPU的并行计算能力,而不需要复杂的线程管理
SIMD与ALU
SIMD(Single Instruction, Multiple Data,单指令流多数据流) 是通过在多个ALU(算术逻辑单元)上同时执行同一指令来实现并行处理的一种技术。在GPU上,SIMD单元能够让多个ALU并行处理数据。SIMD的宽度决定了同时可以处理多少个数据项,它取决于ALU的数量
-
ALU(Arithmetic Logic Unit)是处理器中的核心组件,负责执行加法、乘法、位运算等基础运算。GPU中多个ALU能够同时工作,实现大规模并行计算
-
控制流单元 是管理指令取指、解码和执行的硬件模块。GPU通常通过简化控制流逻辑来减少硬件开销,从而把更多资源用于数据处理
例如,在向量加法中,如果SIMD的宽度为4,则可以将整个循环分为四个部分并行执行。就像农场工人同时用两只手摘苹果一样,SIMD的宽度越大,处理能力越强