
通过将巨大的计算问题划分给多个处理器来解决,这一前景是现代科学发现的基石,它使得从星系形成到生物系统的各种模拟成为可能。这种被称为并行科学计算的方法,使我们能够应对单个计算机无法克服的挑战。然而,仅仅增加处理器并非万能药。通往有效并行计算的道路充满了挑战,从限制加速比的固有串行瓶颈,到协调数千个计算工作单元所需的复杂通信与同步之舞。理解这些限制与利用其潜力同等重要。
本文对这一强大的范式进行了全面的概述。在第一章“原理与机制”中,我们将剖析支配并行性能的基本规则,探讨阿姆达尔定律、区域分解和负载均衡等核心概念。随后,在“应用与跨学科联系”中,我们将看到这些原理如何应用于解决不同领域的实际问题,展示并行计算对科学和工程的深远影响。
想象一下,你身处一个宏伟的图书馆,任务是抄写一部巨型百科全书。如果你独自一人逐页抄写,将耗费一生时间。但如果你雇佣一千名助手呢?你可以给每位助手一卷书,他们可以同时工作。任务将在很短的时间内完成。这,本质上就是并行科学计算的精神:将一项巨大的任务分配给许多工作者——在我们的例子中,是计算机处理器——以便在合理的时间内完成它。
但正如任何大规模协作一样,魔鬼在于细节。你如何划分工作?工作者们如何协调?当一部分工作依赖于另一部分时会发生什么?探索这些问题将我们带入现代计算的核心,揭示出那些优雅简洁而又影响深远的原理。
首先,我们注意到“同时做事”可能意味着几种不同的情况。
第一种,我们称之为数据并行。这就像我们图书馆的例子:每个人都在做完全相同的任务(抄写),但处理的是不同的数据(不同的卷册)。在科学计算中,这可能意味着将相同的物理定律应用于空间中数以万亿计的不同点,或者在数百万像素上运行相同的图像滤镜。由于任务是独立的,这种工作是“易并行”的。
然后是任务并行,这更像是编排一出复杂的舞台剧。你有演员、灯光师和音响师,他们都在执行不同的任务。他们的工作仍然是并行的,但由一个剧本协调。第二幕的灯光提示必须在幕布升起后发生,而演员在登上舞台前不能说出台词。这些依赖关系形成了一个复杂的网络。在计算中,我们可以将其建模为一个有向无环图 (DAG),其中每个节点是一个任务,箭头表示哪些任务必须在其他任务开始前完成。例如,一个复杂的图像拼接程序可能会使用数据并行来处理单个照片区块,然后使用任务并行来管理将它们融合成无缝全景图的复杂、充满依赖性的过程。这种编排是在最大化并行工作和尊重问题固有逻辑顺序之间的精巧舞蹈。
那么,如果我们有一千个处理器,我们能把问题解决得快一千倍吗?这似乎合乎逻辑,但一位名叫 Gene Amdahl 的睿智计算机架构师指出了一个微妙而强大的限制。
想象一下你正在准备一场盛大的宴会。大部分工作,如切菜和摆桌子,可以通过雇佣更多厨师和员工来并行化。但有些部分是顽固的串行任务。你只有一个烤箱,烤主菜需要一个小时。无论你雇佣多少厨师,整个宴会的准备时间永远不会少于那一个小时。
这就是阿姆达尔定律 (Amdahl's Law) 的精髓。每个计算问题都有一部分工作负载是天生串行的——也许是读取输入数据、初始化模拟或整合最终结果。这个串行部分就像一个瓶颈,无论多少并行处理都无法逾越。
让我们把这个概念具体化。假设我们有一个经济模拟,在每个时间步中,80% 的工作是更新个体代理(一个完全并行的任务),20% 的工作是清算全球市场(一个需要所有信息的串行任务)。如果这在单个处理器上需要 100 秒( 用于代理, 用于市场),我们能让它变得多快?
使用 个处理器,并行部分需要 秒,但串行部分仍然需要 秒。总时间是 。 加速比,我们定义为原始时间与新时间的比率,是 。
当我们使用真正海量的处理器,即 时,会发生什么? 这一项趋近于零,总时间接近串行时间,即 秒。因此,可能的最大加速比是 。我们可以快五倍,但不能再快了。即使拥有一百万个处理器,我们还是被卡住了。这个限制因素,即串行部分所占比例的倒数(),严酷地提醒我们,程序中不可并行的部分对性能拥有最终的决定权。这种固定总问题规模并增加处理器数量的分析类型,被称为强扩展性 (strong scaling)。
曾有一段时间,阿姆达尔定律似乎为大规模并行计算的未来蒙上了一层阴影。如果加速比如此有限,那么建造拥有数万个处理器的计算机又有什么意义呢?
思维上的突破来自 John Gustafson,他意识到我们可能问错了问题。当我们得到一台更强大的计算机时,我们通常不只是更快地解决同一个旧问题。我们利用新获得的能力来解决一个更大、更详细、更宏伟的问题。气候科学家不只是想在一半的时间内得到昨天的天气预报;他们想要一个更高分辨率的下个世纪的天气预报。
这就是弱扩展性 (weak scaling) 的观点:我们随着处理器数量的增加而扩展问题规模,目标是保持实际运行时间不变。我们不是在问:“我能多快完成这个固定的任务?”而是在问:“在相同的时间内,我能处理多大的任务?”
让我们回到宴会的例子。如果我们雇佣更多厨师,我们不是更快地准备同一份餐点,而是决定举办一场更大规模的宴会,增加更多菜肴和客人。烤主菜(我们的串行部分)仍然需要一个小时。但是总工作量已经大大增加,所以等待烤箱的总时间所占的比例现在变得小多了。
在科学计算中,这是一个常见的情景。考虑一个使用自适应网格加密 (AMR) 的模拟,计算机在发生有趣现象的区域增加更多的网格点。当我们增加处理器数量 () 时,我们就有能力进行更多的加密。可能代码的串行部分(比如一些全局协调)保持不变或增长非常缓慢,而并行工作的量则与 成比例增长。
在这种情况下,新的、扩展后问题的串行部分比例,我们称之为 ,实际上可能随着 的增长而减小。由此产生的“扩展加速比”实际上可以几乎与 呈线性增长,描绘出一幅乐观得多的图景。阿姆达尔定律和古斯塔夫森定律并不矛盾;它们是同一枚硬币的两面,为问题规模、时间和工作者数量之间的永恒权衡提供了不同的视角。
让我们来谈谈实际操作。我们究竟如何将一个问题,比如模拟机翼上的气流,分配给数千个处理器?最常见的策略是区域分解。我们将正在模拟的物理空间分割成更小的子区域,并将一个子区域分配给每个处理器。
考虑一个基于立方体网格单元的三维天气模型。每个单元的未来状态(例如,温度、压力)是根据其当前状态及其直接邻居的状态来更新的——这是一种所谓的模板计算。如果我们将这个网格划分给许多处理器,一个处理器可以愉快地更新其所有的“内部”单元。但是位于处理器分配区域边缘的单元怎么办?它的更新需要来自邻近处理器所拥有的单元的值。
这就产生了通信需求。一种天真的方法是,每当需要数据时,处理器都向其邻居请求,这样做会非常缓慢。解决方案是一种基于“幽灵”抽象的巧妙约定。每个处理器在其本地区域周围分配一个额外的、不属于自己的单元缓冲区——一个幽灵层。在主计算开始之前,所有处理器都参与一次光环交换。每个处理器复制其边界单元(其光环)的数据,并将其发送给邻居,邻居接收这些数据并填充它们的幽灵层。
一旦交换完成,每个处理器就拥有了为其所有所属单元计算一个完整时间步所需的所有数据,它将幽灵单元视为自己的单元。它可以自主工作,无需进一步交谈,直到下一次光环交换。
整个方案的效率取决于一个简单的几何原理:最小化通信与计算之比。计算量与子区域中的单元数量(其体积)成正比。通信量与需要交换的表面单元数量成正比。为了提高效率,你希望你的子区域尽可能“ chunky ”(像立方体),而不是像薄饼或长针——以最大化体积与表面积之比。对于固定的处理器数量 ,二维或三维分解几乎总是优于一维“板状”分解,因为它在相同工作量的情况下边界要小得多。
如果问题是均匀的——即每个单元需要相同数量的计算工作——这种几何剖分效果非常好。但如果不是呢?在一个模拟星系合并的天体物理学模拟中,大部分空间可能是空的,计算成本很低,而所有的活动——以及所有的工作——都集中在几个具有复杂物理过程的、小而密集的区域。
如果我们只是将区域切成大小相等的几何块,一些处理器会分到“重”的部分,被工作淹没,而另一些处理器则分到“轻”的部分,很快就完成了。然后,快的处理器将处于空闲状态,在同步点等待最慢的那个处理器完成。这被称为负载不均衡,它是并行效率的主要杀手。船队的速度取决于最慢的那艘船。
为了解决这个问题,我们需要一种更智能的剖分方法。我们可以将问题抽象为一个图,其中每个网格单元是一个顶点,连接任意两个相互依赖的单元的边。我们可以为每个顶点分配一个权重,代表其计算成本。现在,区域分解问题变成了计算机科学中一个著名的问题:图划分。目标是将图切成 个部分,使得每个部分中顶点权重的总和尽可能相等(平衡负载),同时最小化被切割的边的数量(或总权重)(最小化通信)。
这种强大的技术允许分区边界完全不规则,蜿蜒穿过区域,以划分出工作量相等而非体积相等的块。一个处理器可能被分配一个非常大的物理区域的空旷空间,而另一个处理器则得到一个微小、密集、高工作负载的区域。图划分是在真实世界科学问题这个崎岖不平、异构的宇宙中实现平衡的关键。在不干扰运行中代码的情况下测量这种平衡本身就是一个深刻的挑战,需要巧妙的度量标准和检测策略。
我们已经看到,并行性能是与许多敌人战斗的结果:串行瓶颈、通信成本和负载不均衡。一个并行作业的总运行时间是理想的、完美扩展的时间加上所有这些开销。即使是像屏障同步这样简单的事情,即所有进程都必须等待最后一个到达才能继续,也会因执行时间中微小的、随机的偏差而引入累积延迟。
在现代超级计算机中,情况变得更加复杂。我们通常有一个并行的层次结构。一台机器可能有很多节点(计算机),每个节点有多个处理器芯片,每个芯片有多个核心。一个常见的编程模型是在节点之间使用一个层次的并行(例如,MPI),在单个节点内使用另一个层次的并行(例如,OpenMP)。这就像一个交响乐团,有不同的声部(MPI 进程),每个声部又有多个音乐家一起演奏(OpenMP 线程)。
这就提出了一个新的优化问题:对于总共 个核心,进程和线程的最佳安排是什么?我们应该有许多小的 MPI 进程,还是少数几个大的?增加更多的 MPI 进程可能会增加通信开销,而增加更多的 OpenMP 线程可能会增加节点上共享内存的争用。
我们可以对这种权衡进行建模。假设 MPI 部分的效率随着我们增加进程数 而以因子 指数衰减,OpenMP 部分随着我们增加线程数 而以因子 衰减。通过将此问题表述为一个简单的优化问题,我们可以找到完美的平衡。最优配置结果是 和 。这个优美的结果给了我们一个深刻的建议:如果节点间的通信非常昂贵(高 ),你应该使用更少、更大的 MPI 进程。如果节点内的争用是更大的问题(高 ),你应该使用更多、更小的进程。
从关于分工的简单观察到在世界最大型机器上平衡工作负载的复杂策略,并行计算的原理是人类智慧的证明。它们是我们必须遵循的规则,用以建造我们的计算望远镜,让我们能够模拟从蛋白质折叠到宇宙形成的一切。
大自然以其宏大而静默的方式,本身就是一台规模难以想象的并行计算机。流动河流中的每一个水分子,旋转星系中的每一颗恒星,思考大脑中的每一个神经元,都根据与邻居的相互作用同时计算着自己的下一个状态。宇宙不会等待一个粒子完成它的任务再处理下一个。科学计算的雄心在于构建机器、编写规则,以捕捉这个广阔并发世界的一部分。在经历了并行计算基本原理的旅程之后,我们现在转向观察这些关于划分、通信和同步的思想如何演变成正在重塑科学与工程各个角落的工具。这不仅仅是一份应用目录;它让我们一窥一种新的计算思维方式如何让我们提出比以往任何时候都更宏大、更深刻、更复杂的问题。
并行计算中第一个、最直观的行动是将一个大问题分解成更小的部分,并将每个部分分配给不同的工作者或“处理器”。想象一下预测天气。你可能会将一张国家地图划分成网格状的方块,并将每个方块分配给不同的气象学家。每个人都在自己的区域内工作,但他们需要与邻居交谈,以了解隔壁吹来什么样的天气。
这个简单的画面抓住了区域分解的精髓。当我们解决一个物理问题时,比如由泊松方程描述的热量或电势分布,我们通常将世界表示为一个精细的点网格。在并行计算机中,我们将这个网格分割成子区域。每个处理器的计算工作量与其子区域内的网格点数——即其“体积”——成正比。然而,通信成本源于在边界处交换信息的需求——即其“表面积”。这个基本的表面积与体积之比是许多并行应用中的核心权衡。对于给定的工作量,我们希望设计我们的子区域,使其边界尽可能小,以最小化花在“交谈”而非“计算”上的时间。
这种整齐的划分对于规则、可预测的问题非常有效。但是,如果我们的“区域”不是一个均匀的网格,而是一堆崎岖不平、不规则的物体呢?考虑一个模拟星系中恒星形成,或一群人在城市中移动的场景。粒子或代理并非均匀分布;它们聚集在密集的团块中。如果我们简单地将空间切成均匀的方块,一些处理器将被工作压垮,而其他处理器则几乎闲置,管理着空旷的空间。这就是负载不均衡的问题。
为了解决这个问题,我们必须更聪明。我们需要能够感知数据的策略,这些策略划分的是工作,而不仅仅是空间。我们可能会使用像递归坐标二分法 (RCB) 这样的方法,它反复地将粒子云对半切割,以确保每个处理器获得相同数量的粒子。或者我们可以追踪一条空间填充曲线 (SFC),这是一条令人费解的线,它以一种能使空间上邻近的点在曲线上也保持邻近的方式蜿蜒穿过空间,然后我们只需将这条线切成等长的段落。我们甚至可以使用像k-均值聚类这样的机器学习技术来找到粒子团的自然中心,并围绕它们划分区域。选择并非随意的;这是一个设计挑战,我们必须通过负载均衡(工作是否公平分配?)和通信成本(相互作用的粒子是否尽可能地保留在同一个处理器上?)这两个指标来对不同策略进行基准测试和衡量其有效性。
这种几何划分有着深刻的代数灵魂。当我们划分一个物理区域时,我们实际上是在对一个代表所有点之间相互作用的单一、巨大的矩阵进行巧妙的行和列重排。通过将一个子区域内的所有未知数归为一组,新矩阵展现出一种块状结构。一个子区域内部的相互作用出现在对角线上的大块中,而子区域之间的通信则表现为更小、更稀疏的非对角线块。这种结构催生了一种强大的策略:首先,在每个子区域内独立解决问题,然后只为它们之间的接口解决一个规模小得多的浓缩问题。这种被称为形成舒尔补 (Schur complement) 的代数简化,是驱动计算地质力学等领域许多高级区域分解方法的数学引擎,我们在这些领域模拟地壳中的应力和应变。
如果我们能无限地划分我们的工作,我们就能瞬间解决任何问题。可惜,现实并非如此仁慈。随着我们增加越来越多的处理器,我们常常会得到递减的回报。这是阿姆达尔定律的一种表现。
想象一个大型社交媒体平台的内容审核团队。他们一部分时间用于个人内容审查,这是一个可以完美并行化的任务——越多的审核员意味着越多的内容被审查。但他们另一部分时间花在一个单一的、强制性的政策更新会议上。这个会议是一个串行瓶颈;无论团队中有多少人,它的持续时间都不会缩短。随着团队规模的扩大,工作中的内容审查部分趋近于零,但会议时间保持不变。最终,整个团队的效率受限于那一个不可并行化会议的持续时间。
这个“串行部分”是并行计算的祸根,它常常源于全局通信的需求。许多算法要求所有处理器在某个时刻停下来,贡献一部分信息,并等待一个全局结果被计算出来。一个常见的例子是两个向量的点积,这是无数科学算法(如最速下降法)中的一个基本操作。为了并行计算它,每个处理器从其本地数据计算一个部分和,然后所有这些部分和必须在一个全局归约中合并。这就像一个巨大的同步屏障。每个处理器上的计算时间可以很好地扩展,为 ,但由网络延迟主导的归约通信时间却以 的速度扩展。对于一个固定的问题规模,当你增加处理器数量 () 时,计算时间骤降,而通信时间缓慢但不可避免地增长。最终,处理器花在等待全局共识上的时间比它们计算的时间还要多,增加更多处理器实际上会使整个系统变慢。
这种紧张关系迫使我们重新评估何为“好”的算法。在串行世界里,我们可能会选择一个复杂的、数学上最优的算法,它需要最少的步骤来达到解决方案。但在并行世界里,这可能是一个陷阱。考虑预处理任务,这是一种加速求解大型线性系统的技术。一种强大的串行方法,如不完全 LU 分解 (ILU),通过创建一个易于求逆的矩阵近似版本来工作。然而,这个过程本质上是串行的——计算一个元素依赖于前一个元素。当并行化时,这种依赖性会产生一个计算的“波前”,它缓慢地在处理器间传播,迫使大多数处理器处于空闲状态。相比之下,像雅可比预处理器这样简单得多的方法,在串行环境中通常被认为是“较弱”的,却是易并行的。它的应用是一个简单的缩放操作,每个处理器都可以同时进行,无需任何通信。令人惊讶的结果是,“更笨”但高度可并行的算法在大规模机器上往往胜过“更聪明”但串行的算法。并行算法设计的艺术不仅仅在于原始的数学效率,而在于在计算和通信之间找到正确的平衡。
并行原理是普适的,但它们在各种惊人的架构和科学领域中找到了不同的表达方式。例如,现代图形处理器 (GPU) 是细粒度并行的奇迹。它包含数千个简单的核心,这些核心以锁步方式在不同的数据片段上执行相同的指令(一种称为 SIMT,即单指令多线程的模型)。为了高效地为此类设备编程,我们必须设计能够分解为数千个相同、独立任务的算法。一个经典的例子是双调排序网络,这是一种用于数字排序的优雅算法,可以完美地映射到 GPU 架构上。该算法由一系列简单的比较-交换步骤组成,这些步骤由屏障同步隔开,其中一个块内的所有线程必须相互等待,直到完成才能进入下一阶段。这种计算与同步的复杂舞蹈,在高速的片上共享内存中进行管理,使得 GPU 能够达到几十年前难以想象的计算吞吐量。
现在,这种力量正被释放到远超图形领域的问题上。在网络科学中,研究人员通过计算其邻接矩阵的特征值来分析像万维网或社交网络这样的大规模图的结构。像幂迭代法这样的算法可以找到最重要的“中心”节点。在并行机器上运行这个算法会揭示新的挑战。核心操作,即稀疏矩阵向量乘积 (SpMV),通常不受处理器计算速度的限制,而是受内存带宽的限制——即它能从内存中获取矩阵数据的速率。此外,真实世界的网络通常是“无标度”的,有少数高度连接的“枢纽”节点。一个简单的划分可能导致极端的负载不均衡,其中一个处理器被一个枢纽及其所有连接卡住,而其他处理器几乎无事可做。解决方案涉及更复杂的二维矩阵划分和基于块的算法,这些算法增加了计算与内存访问的比率,将一个内存受限的问题转变为一个计算受限的问题。
也许最令人兴奋的前沿是在多尺度建模中,我们模拟跨越巨大空间和时间尺度的系统。在计算生物学中,人们可能会建立一个组织发育的混合模型。在粗糙网格上,一个偏微分方程 (PDE)可以描述化学形态发生素的扩散。同时,数百万个单独的细胞,被建模为基于代理的模型 (ABM) 中的代理,移动、分裂并对化学浓度做出反应。并行的挑战是巨大的:我们有两个必须耦合在一起的不同计算模型。最有效的策略是协同定位:确保一个代理及其所在的网格单元由同一个处理器拥有。这最大限度地减少了昂贵的、非结构化的通信。但是当代理移动或增殖时会发生什么?计算负载会转移,一个曾经平衡的划分变得不平衡。解决方案是动态负载均衡,即模拟定期暂停以重新划分区域,在处理器之间迁移代理和网格元素以恢复平衡。这些复杂的技术使我们能够构建虚拟实验室,用于探索生命系统复杂、涌现的行为。
最后,当我们推向最大规模时,一个新的挑战出现了:正确性和可复现性。在宇宙学中,朋友的朋友 (FoF)算法通过将彼此靠近的粒子分组来识别星系和暗物质团。当在数万个处理器上运行时,粒子分组的顺序及其属性(如质量或质心)的求和顺序可能会因网络计时的微小变化而每次运行都不同。由于浮点运算不是完全关联的—— 并不总是与 按位相同——这可能导致非确定性的结果。在不同数量的处理器上进行的两次运行可能会产生略有不同的晕质量,甚至不同的晕目录。对于科学来说,这是不可接受的。实现按位确定性需要极大的算法上的谨慎:使用规范的决胜规则(比如选择具有最小全局 ID 的粒子作为组的代表)并为所有归约操作强制执行一个固定的顺序(例如,在求和前按 ID 对粒子进行排序)。这种严谨性确保了模拟结果仅仅是科学本身的函数,而不是运行它们的并行机器的产物。
从简单的网格到活体组织再到宇宙网,并行计算的线索贯穿于现代科学。它是一个源于简单需求——追求更快——的领域,现已成熟为一个丰富的独立学科。它告诉我们,最快的路径并非总是最直接的,最好的算法并非总是最显而易见的,而划分问题仅仅是旅程的开始。真正的艺术在于管理不可避免的通信、同步以及硬件和软件之间微妙的相互作用,所有这些都是为了构建一个更可计算的宇宙。