try ai
科普
编辑
分享
反馈
  • 异步计算

异步计算

SciencePedia玻尔百科
核心要点
  • 异步计算用灵活的事件驱动模型取代了僵化的全局时钟,极大地提高了能效和性能。
  • 其核心优势是“延迟隐藏”,即网络通信或磁盘 I/O 等慢速操作与独立的计算工作重叠进行。
  • 该范式需要鲁棒的协议来管理诸如竞争条件、死锁以及浮点计算中的不可复现性等挑战。
  • 异步是现代计算的一项基本原则,支撑着从 CPU-GPU 协同到大规模科学模拟和类脑人工智能等方方面面。

引言

在计算历史的大部分时间里,进步一直伴随着全局时钟的稳定节拍,这是一种同步范式,它以牺牲效率为代价带来了秩序。这种每个组件都步调一致的僵化方法,越来越难以应对现代计算的需求,造成了瓶颈并浪费了能源。异步计算提供了一种革命性的替代方案:一个由事件而非通用计时器驱动的世界。这种转变为同步模型的固有局限性提供了解决方案,为构建更快、更节能、更能处理当今复杂并发任务的系统铺平了道路。

本文探讨了异步计算的强大世界。在第一部分“原理与机制”中,我们将解构同步模型,以理解为何放弃时钟如此有益。我们将深入研究事件驱动范式、在单线程上管理并发的艺术、隐藏延迟的关键技术,以及避免死锁和竞争条件等陷阱所需的基本协议。随后,“应用与跨学科联系”部分将展示这些原理在现实世界中的应用,从优化算法和超级计算机模拟,到构建类脑神经网络,甚至为复杂的经济系统建模。

原理与机制

要真正领会异步世界,我们必须首先理解它试图取代的世界:同步世界,一个由稳定、无情的全局时钟滴答声所支配的领域。

时钟带来的安逸

想象一台巨大而精密的机器,比如一个现代计算机处理器。在内部,数十亿个微小的开关,即晶体管,协同工作。它们如何保持同步?在计算历史的大部分时间里,答案一直是​​全局时钟​​。它是系统的节拍器,一个脉冲信号被分发到芯片的每个角落,规定了事情可以发生的确切时刻。所有状态变化都与此时钟信号的上升沿或下降沿对齐。程序员可见的世界在一系列离散的、编号的步骤中展开:时间 t=0t=0t=0,然后是 t=Tt=Tt=T, t=2Tt=2Tt=2T,依此类推,其中 TTT 是时钟周期。

这种方法有一种美妙而朴素的简洁性。它对所有架构可见的事件施加了​​全序​​。事件5发生,然后是事件6,然后是事件7,就这样。没有任何歧义。这种严格的排序是驯服复杂性的有力工具。它在架构层面有效地消除了一整类被称为​​竞争条件​​的问题,即计算结果可能取决于两个竞争信号的不可预测的时序。如果两个信号正在奔向一个目的地,时钟就像一条终点线,仅在它们都稳定下来的特定瞬间对它们进行采样。这保证了​​确定性​​:对于给定的输入序列,输出序列将完全相同,每一次都如此。

你可以把它想象成工厂里一个高度结构化的、时间触发的流水线。早上9:00:00,A工位得到一个零件。9:00:01,它将其传递给B工位。9:00:02,B工位将其传递给C工位。这个过程是完全可预测的。但这种可预测性是有代价的。如果B工位在半秒内完成了工作怎么办?它仍然必须空闲等待,直到时钟敲响9:00:02。如果一个关键零件延迟了怎么办?整条生产线可能都得停下来。时钟是一个暴君,尽管是善意的。它提供了秩序,但可能极其低效。

没有指挥的生活:事件驱动范式

如果我们敢于扔掉时钟会怎样?如果不是每个组件都跟着全局节拍走,而是每个组件仅在有事可做时才行动,由传入的一条信息——一个​​事件​​——触发,那会怎样?这就是​​异步计算​​背后的革命性思想。

在这个世界里,时间不是一系列离散的滴答声,而是连续的流动。信息不是通过时钟滴答 kkk 时寄存器处于什么状态来编码,而是通过事件——比如合成神经元中的电压脉冲——发生的确切时刻来编码。一个组件静静地坐着,几乎不消耗能量,直到一个事件到来。该事件触发一连串的局部活动,这又可能生成新的事件,传播到其他组件。

这种方法最惊人的好处是能源效率。大型同步芯片中的全局时钟是一个巨大的耗电大户。将高频信号分布到广阔区域需要为巨大的电容充放电,而且这在每一个周期都会发生,无论是否有用的工作在进行。这就像把一栋摩天大楼里每个房间的灯都开着,一整夜。在异步系统中,功耗与活动成正比。如果事件稀疏,功耗就很低。

考虑一个拥有一百万个处理节点的大型神经形态结构。如果这个系统是同步的,一个高速时钟会不断地消耗电力,相当于一个固定的能量成本,分摊到有用的事件上。如果事件很罕见,比如每个节点每秒一个,那么时钟为每次有效计算所“浪费”的能量可能是巨大的——在现实场景中,比实际计算本身的能量大数千倍!在异步设计中,功率预算主要由有用的工作决定。这不仅仅是一个增量改进;这是计算物理学上的根本性改变。

杂耍的艺术:单线程上的并发

异步最强大的应用之一在于单个工作者——单个CPU核心上的单个执行线程——如何同时管理多个任务。这就是​​并发​​与​​并行​​的区别。并行是同时做多件事,这需要多个工作者(例如,多个CPU核心)。并发是在同一时间段内管理多个任务,并在它们之间智能切换的艺术。

想象你在做晚饭。你把一锅水放在炉子上烧开。你会站在那里看十分钟吗?当然不会。你会开始切蔬菜。当水壶鸣笛(一个事件!),你再把注意力转回到锅上。你一直在并发地处理两项任务——烧水和切菜——尽管你只有一双手。你通过不​​阻塞​​来实现这一点;你没有让慢速任务(等水烧开)独占你的注意力。

这正是现代事件驱动服务器的工作方式。当一个需要从慢速磁盘读取文件的请求到达时,单线程事件循环向操作系统发出一个​​非阻塞​​I/O命令。这个命令就像把锅放在炉子上。操作系统在后台处理磁盘读取,并承诺在数据就绪时通过一个事件通知循环。事件循环不必等待,而是立即可以自由地处理其他请求——也许是一个只涉及快速CPU计算的请求。它同时处理多个“进行中”的请求,即使只有一个并行单元,也能实现高并发。我们甚至可以衡量这种“并发深度”,例如,通过计算一段时间内进行中的I/O操作的平均数量。

重叠的魔力:隐藏延迟

厨师一边烧水一边切菜,不仅仅是为了让自己忙起来;她正在隐藏烧水任务的延迟。准备这顿饭的总时间不是(烧水时间)+(切菜时间),而是更接近两项任务中较长的那一个。这种“免费”的重叠是异步编程的圣杯,尤其是在高性能计算中。

考虑一个在超级计算机上运行的大规模科学模拟,其中一个大型三维网格被划分给数千个处理器。为了计算其本地网格边缘的值,每个处理器需要来自其邻居网格的数据(一个“光环”)。一个朴素的同步方法是:1. 请求数据并等待。2. 接收数据。3. 计算。

异步方法要优雅得多。在时间步开始时,处理器向其邻居发布对其所需的所有光环数据的非阻塞请求。这些请求,由称为​​future​​或request的处理器表示,是数据最终会到达的承诺。关键是,处理器不等待。它立即开始计算其网格的内部,即不依赖于光环数据的部分。计算与通信同时发生。

该步骤的总时间不再是计算和通信时间的总和,Tcomp+TcommT_{\text{comp}} + T_{\text{comm}}Tcomp​+Tcomm​,而是两者中较长的一个,max⁡(Tcomp,Tcomm)\max(T_{\text{comp}}, T_{\text{comm}})max(Tcomp​,Tcomm​)(加上最终边界更新的时间,这取决于通信)。我们成功地“隐藏”在计算背后的通信时间量是 min⁡(Tcomp,Tcomm)\min(T_{\text{comp}}, T_{\text{comm}})min(Tcomp​,Tcomm​)。我们可以用非常简洁的方式表达这一点:我们可以隐藏的通信延迟的分数 HHH,由 H(χ)=min⁡(1,χ)H(\chi) = \min(1, \chi)H(χ)=min(1,χ) 给出,其中 χ=Tcomp/Tcomm\chi = T_{\text{comp}}/T_{\text{comm}}χ=Tcomp​/Tcomm​ 是计算与通信之比。如果你的计算量大于通信量(χ≥1\chi \ge 1χ≥1),你就可以免费隐藏整个通信延迟。

精妙的舞蹈:异步安全的协议

这种新获得的自由和力量伴随着责任。在一个没有全局指挥的世界里,舞者们必须遵守严格的协议,以避免相撞或永远等待一个永远不会到来的舞伴。

一个常见且危险的错误是通信缓冲区上的​​竞争条件​​。想象一个程序员编写了代码,从一个缓冲区发出一个非阻塞的数据发送,然后,以为发送已经“完成”,立即开始在同一个缓冲区中为下一步写入新数据。这是一场灾难!非阻塞调用只启动了发送;系统可能仍在从该缓冲区读取数据。在发送中途修改它会导致数据损坏。基本规则是:​​一个被赋予非阻塞操作的缓冲区,在完成调用(如 MPI_Wait)确认操作完成之前,绝不能被触碰​​。标准的解决方案是​​双缓冲​​:使用两个缓冲区,交替使用。当系统从缓冲区A发送数据时,你可以自由地在缓冲区B中进行计算。

另一个危险是​​死锁​​。想象两个进程,P1和P2,每个都需要向对方发送数据并从对方接收数据。如果两者都决定首先执行一个阻塞发送,它们就进入了一个致命拥抱。P1被阻塞,等待P2发布一个接收。P2也被阻塞,等待P1发布一个接收。两者都无法继续,程序冻结。一个防止这种情况的鲁棒协议是打破依赖循环:​​总是在发起任何发送之前发布所有的接收​​。通过首先表明你准备好接收,你确保了伙伴随后的发送总会有一个匹配的接收在等着它。

最后,还有一个与​​进展​​相关的微妙性能陷阱。你可能启动了一个非阻塞操作,然后开始一个长的、纯计算的循环。你假设通信在后台进行,但在许多高性能系统中,通信引擎只有在你显式调用其某个函数时才会取得进展。你的长计算循环饿死了通信库,你所设计的“重叠”消失了——通信只在你最终调用 MPI_Wait 时才在一瞬间发生。解决方法是在计算循环中定期“戳一下”库,使用像 MPI_Test 这样的非阻塞测试函数,给它一个工作的机会。

驯服混乱:对可复现性的追求

也许放弃时钟的全序所带来的最深远后果是,事件的确切顺序可能在每次运行时都发生变化。网络流量或操作系统调度的微小变化可能会改变哪条消息先到达。对于许多应用来说这无关紧要,但在科学计算中,这可能是一个微妙的噩梦。

原因在于浮点算术的本质。我们在学校里学到,加法满足结合律:(a+b)+c=a+(b+c)(a+b)+c = a+(b+c)(a+b)+c=a+(b+c)。但对于使用有限精度浮点数的计算机来说,这并非事实。由于每一步的舍入,以不同顺序对同一组数字求和可能会产生比特级别上不同的结果。在一个大规模异步模拟中,数百万个值在数千个处理器上求和,求和的顺序几乎可以肯定在每次运行之间是不同的。这意味着你的模拟不是​​比特级可复现​​的,这对于调试和验证来说是一场灾难。

你如何驯服这种混乱并恢复确定性?你无法强迫顺序。解决方案必须是使用一个真正满足结合律的操作。答案不是用浮点加法来进行求和。相反,可以使用一种称为​​超级累加器​​的巧妙数据结构。这本质上是一个大的整数计数器数组,其中每个计数器对应一个特定的浮点指数。每个要相加的数字被分解为其尾数(一个整数)和指数,然后将尾数加到相应的整数计数器上。整数加法是完全满足结合律的。在所有进程的所有数字都被添加到超级累加器中(使用可复现的整数归约)之后,精确的和在一个最后的、单一的、确定性的舍入步骤中被转换回标准的浮点数。

从简单、僵化的时钟世界到复杂、流畅、强大的异步世界的这段旅程,揭示了计算中的一个基本权衡:秩序与效率、确定性与灵活性之间的张力。掌握异步计算就是要深刻理解其原理,以便利用其力量,同时通过谨慎、鲁棒的协议来尊重其危险。

应用与跨学科联系

在了解了异步计算的原理之后,我们可能会觉得对这片新领域有了一张可靠的地图。但是,一张地图,无论多么详细,都不是地貌本身。真正的乐趣来自于探索,来自于看到这些抽象思想如何为我们构建的机器注入生命,甚至提供一个审视世界的新视角。异步思维不仅仅是一种巧妙的工程技巧;它反映了一个并非按照单一鼓点前进的宇宙。一个系统的不同部分,无论是计算机芯片还是国民经济,都以各自的步调演进。通过拥抱这种固有的异步性,我们设计的系统不仅能更快,而且更鲁棒、更高效,并且在某种奇怪的方式下,更自然。

让我们踏上这片地貌的旅程,从我们计算机中的硅芯片核心,到塑造我们生活的庞大复杂系统。

机器的核心:反思算法与系统

异步思维最直接的影响在于我们软件的逻辑本身。考虑一个像给一列数字排序这样基础的任务。传统的同步方法就像一个一丝不苟的教官,命令每一步,并等待其完成后再发出下一个指令。而异步方法更像一个明智的管理者。如果你需要整理一大堆文件,你不会一份一份地做。你会把这堆文件分成两半,把一半给一个同事,然后说:“你整理你那半,我整理我这半。我们都弄完后在这里碰头。”

这正是异步归并排序背后的思想。对数组的两半进行排序的递归调用不是一个接一个地执行。相反,它们是并发地启动的。程序不等待;它让两个子问题以各自的速度进行。唯一的同步点是最后的归并步骤,它必须等待两个排好序的半部分都准备好。如果其中一半“更容易”排序并提前完成,它只需等待。没有时间被浪费。这种分治策略,在并发的加持下,是并行计算的基石。

这种“分派后继续”的原则是现代计算机系统的命脉,尤其是在中央处理器(CPU)和图形处理器(GPU)的协作中。CPU 是一个多才多艺的通才,而 GPU 是一个并行处理的强大动力源,擅长同时对数千个数据点执行相同的操作。当你玩视频游戏时,CPU 并不对 GPU 进行微观管理。相反,它充当生产者,迅速地用任务填满一个“命令缓冲区”:“画这个三角形”,“应用这个纹理”,“计算这个光照”。GPU,作为消费者,以自己的节奏处理这个命令队列。这种通过一个简单的 FIFO 队列介导的异步交接,使得两个处理器都能在不互相等待的情况下充分发挥其潜力。这是我们常常习以为常的惊艳实时图形背后的基本编排。

也许计算中最显著的“等待游戏”是输入/输出(I/O)。一个现代处理器在从硬盘驱动器获取单个数据片所需的时间内可以执行数十亿次计算。一个读取数据、处理它、再读取下一片、再处理它的同步程序,大部分时间都在空闲,等待慢速的磁盘。异步 I/O 提供了一个优雅的解决方案:双缓冲。想象一下朗读一本书。你不是读完一页,然后翻到下一页,再读那一页,而是可以有一个助手,在你即将读完当前页时,就把下一页放在你面前。你永远不必停止阅读。这正是一个系统在将 I/O 与计算重叠时所做的事情。它在 CPU 忙于处理当前数据块时,请求从磁盘读取下一个数据块。当 CPU 完成时,下一个数据块已经等在内存中了。这个隐藏延迟的简单思想使得视频流、大规模数据分析和科学模拟成为可能。

模拟宇宙:大规模异步

当我们从单台计算机转向世界上最大的超级计算机,用于模拟从天气模式到星系形成的一切事物时,异步的原则变得更加关键。这些大规模模拟通过将一个大的物理域——一片大气层,一个宇宙区域——分解成数百万个更小的子域,每个子域分配给一个不同的处理器来工作。

在模拟的每个时间步,一个处理器需要知道其直接邻居的状态来计算自己的未来状态。这个边界信息被称为“光环”。一种朴素的同步方法是:(1)所有处理器停止,(2)所有处理器交换光环数据,(3)所有处理器等待交换完成,以及(4)所有处理器计算下一步。这涉及到大量的等待。

异步策略要智能得多。在时间步开始时,每个处理器立即并行启动两个任务:它向邻居发起对光环数据的非阻塞请求,并同时开始计算其自身子域的内部部分的更新——即不依赖于光环数据的部分。对内部的计算有效地“隐藏”了光环数据通过网络传输所需的时间。只有当内部计算完成时,处理器才检查光环数据是否已到达。一旦到达,它就可以计算其边界区域的更新并完成该时间步。这种重叠通信和计算的技术是大规模科学计算中最重要的单一优化。在一些高级模拟中,处理器甚至必须同时处理多个异步任务,例如在通过网络进行光环交换的同时,将模拟结果写入并行文件系统,这需要仔细管理不同的通信信道以避免干扰。

这引出了一个更深、更微妙的问题。如果我们不等待最新的光环信息会怎么样?在许多迭代算法中,例如那些用于解决物理模拟中出现的庞大线性方程组的算法,我们有时可以使用来自邻居的稍微过时的,或“陈旧”的信息。一种异步迭代方法可能允许每个处理器继续前进,使用它当前拥有的任何版本的邻居数据来计算新的更新。这在每一步都会引入小误差,算法可能需要更多的迭代才能收敛到最终答案。然而,由于每次迭代都快得多(因为没有等待通信),求解总时间可能会大大减少。这种权衡——牺牲每次迭代的精度来换取原始速度——是一个引人入胜且活跃的研究领域,推动着科学计算可能性的边界。

超越时钟滴答:异步事件中涌现的秩序

世界不是一系列离散、同步的步骤。它是一个相互作用的、异步事件的连续流。一些最令人兴奋的异步计算应用拥抱了这种事件驱动的现实。

考虑使用一种称为动力学蒙特卡洛(KMC)的方法来模拟原子级别的材料行为。在这个模型中,原子并非同时移动。相反,单个“事件”——一个原子跳到新位置,一个缺陷迁移——在随机的时间发生,由概率决定。并行模拟必须正确地再现这种随机的、因果的结构。材料某部分的事件可以改变其他地方事件发生的概率。一个真正精确的异步并行 KMC 算法必须强制执行这种因果关系,确保没有“果”在其“因”之前被处理,即使跨越不同的处理器。这是一个深刻的挑战,将并行计算与物理过程中的基本因果结构联系起来。

这种事件驱动的范式在人脑中找到了其最引人注目的生物学类比。大脑不是一台同步机器。神经元通过在特定的、不规则的时间点发送离散的电脉冲,或“脉冲”来通信。脉冲神经网络(SNNs)是一类受此原理启发的新型人工智能模型。在 SNN 中,计算是稀疏且事件驱动的:一个神经元只有在接收或发送脉冲时才工作。在这种网络中的学习也必须是异步和局部的。一个突触的权重更新不能依赖于网络的全局状态;它只能依赖于局部到达的脉冲历史。这导致了优雅且高效的学习规则,完全在异步、事件驱动的领域中运行,预示着一个超低功耗人工智能的未来。

最后,我们可以将视野放大,将异步不仅看作一种计算策略,而且看作是复杂系统的描述模型。想象一个去中心化的市场经济。没有全局时钟,没有中央拍卖师告诉每个人何时行动。每个代理人——个人、公司——都根据自己的私有信息和自己独特的策略(其“指令”)来操作。他们与一组有限的其他代理人互动,信息(价格、订单)通过网络以延迟的方式传播。这个系统,拥有大量独立代理人异步运行不同程序,是多指令、多数据(MIMD)并行计算机的一个完美的现实世界类比。无数异步决策看似混乱的相互作用,催生了市场涌现的全局秩序。这个类比揭示了异步范式的真正力量:它是一种描述局部、独立行动如何交织在一起创造一个复杂、功能完整的整体的语言,无论这个整体是解决一个方程的超级计算机,是处理信息的大脑,还是一个自我组织的社会。