
在多核处理器和大规模超级计算机的时代,仅仅编写正确的代码是不够的;我们必须编写能够利用并行执行快速运行的代码。然而,为单处理器设计的传统算法效率分析方法在这一新形势下显得力不从心。核心挑战在于理解如何有效划分工作,以及算法的内部依赖性如何对加速比构成根本限制。我们如何在运行一个算法之前,就能预测它在并行机器上的性能呢?
本文介绍了工作-跨度模型,这是一个优雅而强大的理论框架,旨在精确回答这些问题。它提供了一种简单而深刻的方法来衡量任何计算的并行潜力。在接下来的章节中,您将对这个重要工具有一个全面的了解。“原理与机制”部分将奠定基础,定义工作量、跨度、并行度的核心概念,以及它们与阿姆達爾定律等基本限制的关系。随后的“应用与跨学科联系”部分将展示该模型的实际效用,演示它如何用于分析经典算法、优化大规模科学模拟,并为硬件和软件的设计提供信息。
要真正理解并行计算的艺术,我们必须首先学会如何衡量它。当我们为单个处理器编写程序时,我们主要关心的是完成任务所需的总步数——即其运行时。但当我们拥有成百上千个处理器时,情况就完全不同了。这不再仅仅是关于总工作量的问题,而是关于如何巧妙地划分这些工作量。
想象一下建造一座房子。衡量项目规模的一种方法是所需总工时。如果需要2000个工时,这就是我们的总成本。但这个数字并不能告诉我们房子能以多快的速度建好。有些任务,比如粉刷不同的房间,如果我们有足够多的油漆工,就可以同时进行。而另一些任务则是严格顺序的:你必须先打好地基才能砌墙,墙砌好了才能盖屋顶。这个不可改变的顺序决定了项目所需的最短时间,无论你雇佣多少工人。
并行计算面临着完全相同的二元性。为了对其进行推理,我们需要两个基本的度量来捕捉问题的这两个方面。这些正是工作-跨度模型的基石。
让我们将任何计算想象成一个操作网络,其中箭头连接着相互依赖的任务。这个依赖关系图在形式上被称为有向无环图(DAG)。工作-跨度模型为我们提供了两个简单而优美的指标来描述这整个复杂的网络。
首先是工作量(work),用 或 表示。这是最直观的度量:它是算法执行的基本操作总数。它是所有计算量的总和,相当于在单个处理器上运行整个程序所需的时间()。在我们建房子的比喻中,这就是总共2000个工时。
其次,是更为精妙的跨度(span),用 或 表示。跨度是 DAG 中最长依赖任务链的长度——即关键路径。它代表了算法不可简化的顺序核心。即使你有无限数量的处理器,算法所需的时间也至少是这么多(),因为这条路径上的操作根本无法并行处理。在我们建房子的比喻中,这就是从铺设第一块基石到敲下最后一块屋顶瓦片,遵循最長必要步驟鏈條所需的时间。
工作量和跨度之间的关系定义了算法的并行潜力。考虑一个算法,其依赖图只是一条长而简单的操作链。 在这里,每个操作都依赖于前一个操作。最长的路径是唯一的路径,所以跨度等于工作量()。即使有一百万个处理器,任何时候也只有一个能处于活动状态。这样的算法是内在地顺序性的。
有了工作量和跨度的概念,我们就可以陈述两个优美而简洁的“法则”,它们支配着在拥有 个处理器的机器上的运行时 。
第一条是工作量法则(Work Law)。 个处理器协同工作,必须完成总共 的操作。在绝对的最佳情况下,如果工作量可以没有任何开销地完美共享,那么所花费的时间将是总工作量除以工人数量。因此,运行时必须至少这么长:
这条法则只是说你无法在所需的总工作量上作弊。
第二条是跨度法则(Span Law)。算法有一条长度为 的关键路径,它代表了一系列严格的依赖关系。再多的处理能力也无法打破这条链。因此,运行时永远不可能比跨度更短:
这条法则告诉我们,每个算法都有一个由其自身内部逻辑施加的基本速度限制,这是一个即使在拥有无限资源的情况下也依然存在的瓶颈。 一个具有大跨度的计算,比如说对于大小为 的输入,跨度为 ,那么无论它有多少工作量或使用多少处理器,其运行时间永远不可能低于线性时间。
将这两者结合起来,我们得到了任何并行算法运行时的强大下限:
这个单一的表达式告诉我们所能期望的最佳情况。实际的运行时将受限于分配工作的需求或关键路径的长度,取两者中的较大者。
并行计算的全部目的就是实现加速比(speedup),即并行算法比其顺序对应版本快多少倍。形式上,加速比是单处理器时间()与 处理器时间()之比:
利用我们的基本法则,我们现在可以找到加速比的最终极限。由于最佳可能时间是 ,因此可实现的最大加速比为:
这是并行计算中最优雅和深刻的结果之一。 它指出你的加速比受限于两件事中的较小者:你拥有的处理器数量()和一个新的、至关重要的量,。
这个比率 被称为算法的并行度(parallelism)。它代表了在关键路径的每一步中可以并行完成的平均工作量。并行度高的算法是“宽”的,有很多并发执行的机会。并行度低的算法(比如我们之前那条简单的链,其中 )是“窄”的,并且是内在地顺序性的。并行度是衡量一个算法是否适合并行计算机的真正标准。
我们甚至可以将此与著名的阿姆达尔定律(Amdahl's Law)直接联系起来。让我们定义算法的“串行分数” 为位于关键路径上的工作量占总工作量的比例。这很简单,就是跨度与工作量的比率:。 那么,受算法并行度限制的最大加速比为:
这就是阿姆达尔定律的结论!如果一个科学模拟有不可避免的全局依赖,形成了一条占总工作量10%()的关键路径,那么即使有无限数量的处理器,你也永远无法实现超过 的加速比。工作-跨度模型在其框架内完美地包含了这一基本限制。
到目前为止,我们讨论的都是最佳情况。在实践中,我们需要一个调度器来将任务分配给处理器。一个好的“贪心”调度器可以实现一个非常接近理想情况的运行时。著名的 Brent 定理 给出了运行时的上限:
其直观解释是,一个好的调度器可以有效地并行化大部分工作,付出大约 的时间成本,但它无法避免长度为 的顺序依赖链。 这个简单的加法公式为预测并行算法(例如生物信息学中使用的分块动态规划)的性能提供了一个强大的工具。
然而,我们必须明智地使用这个公式。它是一个绝佳的近似值,但并不精确。 在真实的机器上,两个隐藏的成本可能会变得非常显著。
这正是为什么工作-跨度模型不仅对于分析,而且对于设计都如此有价值。它告诉我们,要编写一个好的并行算法,我们不仅要控制总工作量 ,还必须努力尽可能地减少跨度 。更小的跨度意味着更高的并行度和更少的同步点,这正是在真实硬件上实现高性能的关键。
让我们通过几个经典算法来看看这些原理是如何运作的。
一个并行前缀和(扫描)(parallel prefix-sum (scan))是科学计算中的一个常见例程,它计算数组元素的累加和。一个朴素的顺序方法具有 和 。但一个聪明的算法将加法操作排列成一棵二叉树。这不会改变总工作量——仍然是 ——但它将跨度显著减少到 。由此产生的并行度是 ,对于大的 来说,这是一个巨大的数值。
另一个漂亮的例子是并行归并排序(parallel merge sort)。标准的顺序归并排序的工作量为 。我们能并行化它吗?通过设计一个非常巧妙的并行归并子程序,我们可以创建一个版本,其工作量保持在接近最优的 ,但跨度被惊人地压缩到 。 这意味着最长的依赖链非常短,从而允许在并行机器上实现巨大的加速比。
这些例子表明,用工作量和跨度的思维方式去思考是一个创造性的过程。这是一个我们可以用来观察计算世界的镜头,一个引导我们发现新的、优雅的方法来释放并行机器力量的工具。通过掌握这两个简单的度量,我们就可以开始驯服并发的复杂性,并构建出不仅正确而且真正快速的算法。
既然我们已经探讨了工作-跨度模型的原理,让我们开始一段旅程,看看它在实践中的应用。你可能会认为,工作量()和跨度()这两个简单的数字过于粗糙,无法捕捉并行计算中错综复杂的动态。但是,一个好的物理定律,或者在这种情况下,一个好的计算模型,其魔力就在于它能够穿透复杂性,揭示一个基本的真理。我们将看到,这对谦逊的数字提供了一个惊人强大的视角,让我们能够理解从排序列表到模拟宇宙等各种计算的并行潜力。
计算机科学的核心是一系列基本的解决问题的策略,即计算的DNA本身。工作-跨度模型使我们能够分析这种DNA,理解哪些算法天生适合并行世界,哪些则注定要走顺序执行的道路。
考虑经典的“分治”策略,归并排序算法是其完美典范。想法很简单:要对一个列表进行排序,将其分成两半,独立地(并且并行地!)对这两个半部进行排序,然后合并两个已排序的结果。对两个半部的递归调用是独立的任务,这是并行化的绝佳机会。但是跨度是多少呢?两个递归排序同时发生,所以该步骤的跨度仅为其中一个的跨度。然而,我们随后必须执行合并操作,这需要它自己的时间。如果我们使用一个巧妙的并行合并方法,合并总大小为 的两个列表的跨度可以小至 。当你把递归的每一层的跨度加起来时,你会发现整个排序的总跨度是 。工作量,即操作总数,仍然是 ,就像它的顺序版本一样。由此产生的并行度 是巨大的,这告诉我们归并排序非常适合并行执行。
并非所有算法都如此合作。动态规划是算法设计的另一块基石,它通常表现出一种不同类型的并行性。以寻找两个字符串的最长公共子序列(LCS)问题为例。标准解决方案涉及填充一个二维表格,其中每个条目都依赖于其邻居。你不能一次性计算所有条目。然而,你可以注意到,一旦前一个“反对角线”完成,表格中沿着同一“反对角线”的所有单元格彼此独立。这创建了一个横扫整个表格的计算“波前”。跨度由这种波前的数量决定,它与字符串长度之和 成正比。总工作量是单元格的总数 。因此,在 个处理器上的并行运行时受限于每个处理器的工作量和跨度,给出的时间大约是 。与分治策略的爆炸性并发相比,这揭示了一种更有限但仍然可观的并行形式。
工作-跨度模型也是诊断缺乏并行性的强大工具。贪心算法在每一步都做出局部最优选择,这常常会创建一个长长的依赖链,从而削弱并行执行。考虑用于数据压缩的霍夫曼编码算法。在每一步,它都会找到频率最小的两个符号并将它们合并。关键问题在于,新合并的节点可能具有非常小的频率,使其成为下一次合并的候选者。这就造成了一种情况,即你必须完全完成第 步才能决定第 步。结果是一个依赖链,在最坏的情况下,其长度为 步,这意味着跨度与符号数量成线性关系。再多的并行处理能力也无法打破这个基本的顺序链。
正是这种行为的多样性使得并行算法设计如此引人入胜。我们可以有具有巨大但效率低下的并行性的算法(如朴素的递归斐波那契求解器),完全没有并行性的算法(如迭代之间存在依赖的简单循环),以及具有结构化、高效并行性的算法(如归并排序)。工作-跨度模型是通用语言,它使我们能够区分这些情况并理解其行为背后的原因。
如果说简单算法是DNA,那么大规模科学模拟就是由这些代码构建的复杂生物体。在这里,工作-跨度模型得到扩展,为一些有史以來最苛刻的计算的性能提供了高层次的见解。
在这些大规模问题中,我们通常无法分析每一条指令。相反,我们可以将计算建模为一个粗粒度任务图。对于像工程模拟中使用的分块 Cholesky 分解这样的复杂算法,我们可能有数千个任务。总工作量 是运行所有任务的总时间,而跨度 是通过任务依赖图的最长路径的时间。仅凭这两个数字,我们就可以得出一个非常强大的加速比预测器:。这个量 被称为平均并行度,代表了你无论投入多少处理器所能期望实现的最大加速比。它告诉我们计算的固有并发性,这个单一的数字指导着算法和运行它的机器的设计。
深入挖掘,该模型揭示了抽象数学与具体性能之间的美妙联系。在稀疏矩阵计算的世界里——这是从流体动力学到结构分析等一切领域的核心——高斯消去法过程可以用一个图来描述。随着消去过程的进行,依赖关系逐渐形成。事实证明,这些依赖关系形成了一种称为消元树的结构。整个并行分解的跨度——其并行执行时间的最终极限——只不过是这棵树的高度!并行潜力并非代码的某种神秘属性;它是从问题本身派生出的一个抽象数学对象的直接、可视化的特征。
在最复杂的模拟中,比如计算天体物理学中的模拟,一个单一的时间步可以是不同并行模式的交响乐。它可能以一个全局快速傅里叶变换(FFT)开始来求解引力,这在宏观层面是高度顺序的。接下来是一个“区域分解”的流体动力学更新,其中宇宙被分成小块,每个处理器并行处理自己的那块。这反过来又为一个辐射传输计算提供数据,该计算可能涉及多个“流水线”横扫计算网格。工作-跨度模型是性能工程师用来将这整个工作流映射为单个有向无环图(DAG),计算每个部分的工作量,并识别关键路径——决定总时间步长持续时间的任务序列——的工具。通过分析这条关键路径,我们可以精确定位瓶颈所在,无论是串行的引力求解还是辐射流水线中的通信延迟,并将我们的优化工作投入到影响最大的地方。
到目前为止,我们的模型一直是抽象的。但当我们将其与硬件的复杂现实和软件的巧妙设计联系起来时,它的真正威力才得以实现。
现代图形处理器(GPU)是并行计算的巨兽,但其性能受制于严格的执行层次结构:线程、线程束和块。工作-跨度模型可以适应这一现实。“跨度”不再是抽象的步数统计,而是具体的处理器周期总和。在GPU上并行地对数字求和这样一个简单的操作也涉及权衡。一种策略可能涉及许多细粒度的步骤,并伴有频繁、昂贵的块级同步(__syncthreads())。另一种更复杂的策略可能会利用线程束内线程的步调一致执行来避免这些屏障,从而显著减少跨度。通过建立一个包含加法和同步的实际周期成本的跨度成本模型,我们可以运用工作-跨度思维来证明第二种策略要优越得多,从而将一个抽象理论转变为编写高性能代码的实用指南。
该模型是如此强大,以至于我们甚至可以将其构建到我们的工具中。想象你是一个编译器,任务是自动并行化一个简单的循环。你该怎么做?如果将循环分解成太多的小任务,调度每个任务的开銷将占主导地位。如果创建的任务太大太少,你将没有足够的并行性来让所有处理器保持忙碌。这是开销和跨度之间的权衡。我们可以根据粒度大小 写出一个总时间的简单成本函数:。任务数量与 成正比,而每个任务的工作量(任务的跨度)与 成正比。通过找到最小化此函数的粒度大小 ,编译器可以做出一个由分析驱动的最优选择。这就是工作-跨度模型,不仅是作为一种分析工具,而且是作为智能系统的设计原则。
工作量、跨度、任务和依赖这些概念并不仅限于硅芯片。它们是并发的通用原则。我们可以使用完全相同的框架来建模一个人类流程,比如科学会议的同行评审工作流。每一份论文提交都会启动一系列任务:预检查、多个独立的评审以及结果汇总。总工作量是所有这些工时的总和。跨度是假设我们有足够多的评审员同时处理所有论文时,一篇论文走完整个流程所需的时间,包括最后的全局决策阶段。这个有趣的例子揭示了一个深刻的真理:工作-跨度模型是推理任何可以同时发生事情的过程的基本方法。它证明了伟大思想的统一力量,向我们展示了支配超级计算机中电子流动的相同原则,也可以描述科学家社区中思想的流动。