《多核与GPU编程:工具、方法及实践》----2.4 程序结构模式

2.4 程序结构模式

模式不仅可以帮助选择合适的工作负载分解方法,还可用于程序的开发,这正是程序结构模式的目标。接下来的一节将讨论和分析几个最著名的模式。
并行程序结构模式可以分为两大类。

全局并行局部串行(Globally Parallel Locally Sequential,GPLS):GPLS表示应用程序可以并发执行多个任务,每个任务串行执行。这类模式包括:

单程序多数据

多程序多数据

主/从

map-reduce

全局串行局部并行(GSLP):GSLP表示应用程序串行执行,当需要时,一些单独的部分可以并行执行。这类模式包括:

fork/join

循环并行

两种分类的区别在图2-12的描述中更加明显。GPLS模式更易于提供更高的可扩展性,尤其对于无共享的体系结构。而GSLP常用于将串行程序变为并行时,并行的通常是最影响性能的部分。

2.4.1 单程序多数据

对单程序多数据(Single Program Multiple Data,SPMD)模式,执行平台的所有节点都运行相同的程序,但它们或者将相同的操作用于不同数据,或者执行程序中不同的执行路径。

将所有的应用逻辑保存在一个程序中促进了更简单及无故障的开发,使SPMD成为受程序员欢迎的选择。典型的程序结构包括下面的步骤。

程序初始化:这个步骤通常包括将程序部署到并行平台,并初始化负责多线程或进程通信及同步的运行时系统。

获取唯一的标识符:标识符通常从0开始计数,枚举使用的线程或进程。一些情况下,标识符可以是向量而不是标量(如CUDA)。标识符生命周期与其标识的线程或进程一致。标识符也可以是持久的,即在整个程序过程中都存在,或者在需要时动态产生。

运行程序:执行与唯一ID一致的执行路径,这里可能包括工作负载或数据分配、角色多样化等。

关闭程序:关闭线程或进程,可能需要将部分结果合并产生最终结果。

SPMD方法很方便,但也有缺点:所有应用程序的代码和静态(即全局)数据都要在所有节点复制。这可能是一个优势,但当所有上述条目都不需要时,这也会是个不足。

2.4.2 多程序多数据

SPMD很灵活足,以覆盖大部分的情况,只有在下列情况下有不足:
若执行平台是易异构的,则需要根据节点的体系结构布局不同的执行文件。

应用程序内存需求非常严苛以至于需要降低上载到每个节点的程序逻辑以保证基本的要件。

多程序多数据(MPMD)模式通过允许可能来自于不同工具链的不同执行文件被组合成一个应用程序来覆盖上述情况。每个计算几点都可以运行自己的程序逻辑处理自己的数据集,但可能仍要遵从前一节识别出的步骤序列。

大部分主要的并行平台都支持MPMD模式,一个特别的例子是CUDA,其程序被编译为单独的文件,但实际包含两种不同的二进制:一个给CPU主机,一个给GPU协处理器。

大部分情况下,只需要将不同执行文件映射到合适的计算节点的配置文件。这种例子将在5.5.2节中展示。

2.4.3 主/从

主/从范例将计算节点的任务分为两种,主节点的职责包括:

将工作发放给从节点

从从节点收集计算结果

代表从节点执行I/O职责,例如给它们发送需要处理的数据或访问一个文件
与用户交互

对最简单的形式,主/从模式只包含一个主节点和若干个从节点。然而,这种安排不能随节点数扩展,因为主节点可能成为瓶颈。这种情况可以使用包含多个主节点的层次化方法,每个主节点控制一部分可用机器,对更高级主节点负责。这种安排如图2-13b所示。

主/从结构概念非常简单,并能自然地应用到许多问题,前提是总体计算能分解为不需要节点间通信的分离且独立的片段。一个额外的好处是这种结构能提供隐式的负载均衡,即它将工作负载分配给空闲节点,以确保工作分配时极少甚至没有不均衡情况。

工作负载可用多种多样的方式描述,从最特殊的,如为已知函数的执行提供参数,到最一般的,如提供具有任何种类计算组合的类实例。

2.4.4 map-reduce

map-reduce是主/从模式一个流行的衍生物,被Google用来运行其搜索引擎后,从此开始流行起来。map-reduce模式应用程序的运行上下文需要通过应用(映射)一个函数来处理大型独立数据的集合(易并行)。所有局部计算的结果需要应用另一个函数进行归约。

map-reduce模式正如Google教程[48]所宣传的,以如图2-14所示的一般形式工作。用户程序产生一个主进程监督整个过程,也会产生一些从进程,这些从进程不仅要负责处理输入数据和中间结果,而且负责合并结果产生最终解答。

在实践中,执行映射和归约阶段的工作者可以相同,两种类型从进程之间的数据存储可以是持久的(如文件)或暂时的(如内存缓冲区)。

map-reduce模式和典型的主/从结构的主要不同在于这种规划允许使用自动化工具,该工具负责应用程序的部署和负载均衡。Apache Hadoop项目是使用map-reduce引擎的一个框架。Hadoop map-reduce引擎提供两种类型的进程:JobTracker以及TaskTracker,前者等价于图2-14中的主线程,后者等价于图2-14中的从线程。这些进程作为系统服务(守护进程)产生。JobTracker负责给TaskTracker分配任务,并追踪它们的进度和状态(例如,如果它们死机,其工作会被调度到其他地方)。每个TaskTracker为分配的任务维护一个简单的先进先出(first-in first-out,FIFO)队列,并作为独立的进程(即单独的Java虚拟机)执行这些任务。

图2-14 map-reduce模式的一般形式。步骤包括产生主进程和从进程,主进程分配任务,由执行映射的从进程输入数据,保存局部结果,⑤归约从进程读取局部结果,⑥保存最终结果

2.4.5 fork/join

并行算法在运行时需要动态创建(fork)任务时可使用fork/join模式。这些子任务(进程或线程)通常需要在父进程或线程恢复执行之前终止(join)。
产生的任务可以通过产生新的线程或进程来运行,或者通过使用存在的线程池来处理它们。后者能最小化创建线程的开销并可能最优地管理机器进程资源(通过将线程数和可用处理器核数相匹配)。

在实际中,一个关于fork/join模式的例子(以并行快速排序算法的实现形式)如下所示:

第6行中的PartitionData函数调用将输入数据分为两个部分,一部分包含小于或等于中间点(pivot)的元素的元素,一部分包含大于或等于中间点的元素。pos索引指向中心点的位置,实际上分了两个部分,分别跨越[0,pos) 和[pos + 1,N)范围。两个部分接续着并行排序,一个通过产生一个新任务(第7、8行),一个使用原本的线程或进程(第9行)。只要被排序的数组大小超过THRES,新的任务就会产生。

重申之前阐述的一点,新任务的产生并不需要创建新线程或进程来处理它。如果要排序的数组非常大,这可能是避免灾难的秘诀,因为此时很有可能使操作系统崩溃。为了说明这可能产生多大的问题,下面考虑在执行代码清单2-8所示的并行快速排序时有多少任务会产生。

如果用T(N)表示输入大小为N时产生的任务总数(去掉第一个,根任务),假设PartitionData函数可以将输入数据分为两个相等的部分(最佳情况),则:

(2-25)
反向代入可以解决这个递推关系。假设N和THRES是2的幂,则当时时以下展开式停止:

(2-26)
将这个k值代入式(2-26),当T(THRES) = 0 时得到:

(2-27)
举例来说,N = 220THRES = 210 ,需要210 - 1 = 1023个任务。一个较好的方法是使用线程池执行产生的任务,这种方法在3.8节做了彻底的探讨。

2.4.6 循环并行

将软件移植到多核体系结构上是一个艰巨的任务。循环并行模式通过允许开发者移植已有的串行代码来解决这个问题,移植过程会并行化支配执行时间的循环。

这种模式对OpenMP平台尤为重要,在这一平台上,在程序员的帮助下,循环半自动地并行化。程序员需要以指令的形式提供提示来帮助完成这个任务。

从提升问题的全新并行解法设计的意义上来说,循环并行模式的可用性有限,该模式注重的是串行到并行解法的演化。这也是性能收益通常较小的原因,但至少需要的开发投入也相应地很小。

时间: 2024-12-03 07:02:44

《多核与GPU编程:工具、方法及实践》----2.4 程序结构模式的相关文章

《多核与GPU编程:工具、方法及实践》----第2章 多核和并行程序设计 2.1 引言

第2章 多核和并行程序设计 本章目标 学习设计并行程序的PCAM方法. 使用任务图和数据依赖图来识别可以并行执行的计算部分. 学习将问题的解法分解为可并发执行部分的流行的分解模式. 学习编写并行软件的主要程序结构模式,如主/从和fork/join. 理解分解模式的性能特点,如流水线. 学习如何结合分解模式和合适的程序结构模式. 2.1 引言 即使是对于经验丰富的专业程序员,向多核编程的过渡也并不简单.多核和并行编程往往会打破语句按严格顺序执行的串行程序的传统风格.当许多事情在同一时间发生时,正如

《多核与GPU编程:工具、方法及实践》----导读

目 录[第1章 概述 1.1 多核计算机时代 ](https://yq.aliyun.com/articles/90097)1.2 并行计算机的分类[1.3 现代计算机概览 1.3.1 Cell BE处理器 1.3.2 NVIDIA Kepler 1.3.3 AMD APU 1.3.4 从多核到众核:Tilera TILE-Gx8072和Intel Xeon Phi ](https://yq.aliyun.com/articles/90111)1.4 性能指标[1.5 并行程序性能的预测与测量

《多核与GPU编程:工具、方法及实践》----1.5 并行程序性能的预测与测量

1.5 并行程序性能的预测与测量 构建并行程序要比串行程序更具挑战性.并行程序程序员需要解决诸如共享资源访问.负载均衡(即,将计算负载分配到所有计算资源上来最小化执行时间)以及程序终止(即,以协调方式暂停程序)等相关问题. 编写并行程序应该首先确定并行能否提升程序性能,加速问题的解决.并行程序的开发成本决定了程序员不可能简单地实现多个并行版本,通过测试找出最佳和最差版本,来评估项目的可行性.虽然对于最简单的问题,这个方法可能是可行的.但是即使能够这样,如果能够确定一个先验的最佳开发路径,并按照这

《多核与GPU编程:工具、方法及实践》----3.2 线程

3.2 线程 3.2.1 线程的定义 线程可以被认为是轻量级进程.更精确的定义是线程是一个执行路径,亦即一个指令序列,可以被操作系统作为整体单元进行管理调度.一个进程中可以有多个线程. 线程可以减轻原有生成进程机制中的开销,仅需要拷贝基本的数据,即运行栈.由于线程包括被调用函数的动态框架(或动态记录),因此运行栈不能被多个线程共享.共享栈意味着控制权可能返回一个与调用线程不同的位置. 当一个进程的主线程(初始线程)生成一个新线程时,最终的内存布局十分类似于图3-3.这里父线程和子线程的关系也是如

《多核与GPU编程:工具、方法及实践》----3.6 monitor

3.6 monitor 信号量提供一个十分方便的机制,如果合适地应用,可以通过多线程的实现来获得最大并行性.然而,这只是"如果".由于它提供十分细粒度且横跨多个线程的程序逻辑并发控制机制,因此高效地使用信号量机制是十分困难的.在面向对象编程时代,信号量已经变得不如从前流行. 幸运的是,存在另一个称为monitor的机制.monitor是一个对象或者模块,可以供多个线程进行安全访问.为了完成这一功能,某个时刻只允许一个线程执行一个monitor的任意一个公有的方法.为了阻塞一个线程或者通

《多核与GPU编程:工具、方法及实践》----2.3 分解模式

2.3 分解模式 设计过程最困难同时也最关键的部分无疑是分解过程,即确定可以并发执行的计算.虽然任务图法是最常用的,但开发者无法从中获取以往的经验,这时就需要模式.Mattson等人[33]列出了若干分解模式(在他们的书中表示为"algorithm structure design space patterns"), 该参考文献包含了工作负载被分解并最终分配到并行或多核平台各个节点上的基本方法.图2-4显示了能得到6个模式之一的决策树. 上一节提到了两类分解,即功能分解和域分解,现在又

《多核与GPU编程:工具、方法及实践》----第1章 概 述 1.1 多核计算机时代

第1章 概 述 本章目标: 了解计算机(计算机体系架构)设计的发展趋势以及该趋势如何影响软件开发. 学习基于Flynn分类的计算机分类方法. 学习评估多核/并行程序性能即加速比和效率的必备工具. 学习测量和报告程序性能的正确实验方法. 学习Amdahl和Gustafson-Barsis定律,并使用这两个定律预测并行程序性能. 1.1 多核计算机时代 在过去的40年中,数字计算机已经成为技术和科学发展的基石.遵循20世纪70年代摩尔(Gordon E. Moore)发现的摩尔定律,计算机的信息处理

《多核与GPU编程:工具、方法及实践》----1.4 性能指标

1.4 性能指标 发展多核硬件和开发多核软件的目标是获取更高性能,例如更短的执行时间.更大规模的问题和更大的数据集等.很明显,这需要一个客观的标准或者准则来评估这些努力的有效性. 最起码,一个并行程序的执行时间应该要比其对应的串行程序的执行时间短(然而,并不是所有情况都这样).执行时间的缩短通常表述为加速比,可用如下公式表达: (1-1) 其中,对于解决同一个问题实例,tseq是串行程序的执行时间,tpar是并行程序的执行 时间. tseq和tpar都是时间,但它们并不总客观.二者会受到如下因素

《多核与GPU编程:工具、方法及实践》----第3章 共享内存编程:线程 3.1 引言

第3章 共享内存编程:线程 本章目标: 学习线程的定义以及创建方法. 学习完成特定任务的初始化线程方法. 学习多种终止多线程程序的技术. 理解多线程访问共享数据过程中的主要问题,例如竞争和死锁. 学习信号量和监视器的定义和使用方法. 熟悉经典同步问题及其解决方法. 学习运行时动态管理线程. 学习多线程程序的调试技术. 3.1 引言 从20世纪60年代麻省理工学院引入兼容分时系统(CTSS)以来,多个程序并发执行的现象已经变得较为常见.操作系统通过中断当前正在执行的程序并将CPU的控制权交由另一个