try ai
科普
编辑
分享
反馈
  • 多核处理器

多核处理器

SciencePedia玻尔百科
核心要点
  • 物理上的“功率墙”终结了单核速度扩展的时代,迫使业界转向多核处理器,并引入了“暗硅”的挑战。
  • 阿姆达尔定律指出,程序在并行硬件上的加速最终受其串行部分的限制,这表明增加更多核心并非总是有益的。
  • 高效的并行计算取决于通过缓存一致性协议(如MESI)管理通信,以及设计可扩展算法以最小化同步和数据争用。
  • 现代处理器为追求更高性能而采用松散内存模型,这要求程序员使用内存栅栏来强制关键的顺序,并确保程序正确性。

引言

几十年来,计算技术的发展遵循着一个简单且可预测的节奏:处理器以指数级速度变得更快,而功耗却没有显著增加,这一现象被称为登纳德缩放(Dennard scaling)。这份“免费午餐”使得软件在变得日益复杂的同时,性能提升可由硬件自动提供。然而,在21世纪头十年中期左右,由于基本的物理限制,这个时代戛然而止,迫使整个行业直面“功率墙”。随着单核性能达到热量上限,计算技术的前进道路从“让一个核心更快”转向“在单个芯片上集成多个核心”。本文将深入探讨由这一转变催生的世界。

向多核架构的转变不仅仅是一次工程上的调整;它是一次范式转移,其影响波及计算的每一个层面,从硅芯片本身到最抽象的算法。核心问题不再仅仅是原始速度,而是协调、通信和效率。我们如何让单个芯片上的几十个甚至几百个“大脑”协同工作,而互不干扰?我们如何重写软件,甚至改变我们对问题的基本思考方式,以利用这个新的并行世界?

在接下来的章节中,我们将踏上解答这些问题的旅程。第一章​​原理与机制​​,将揭示多核处理器的基本概念,探索功耗的物理学、并行加速的极限、缓存一致性的复杂舞蹈以及内存一致性的令人费解的现实。随后,关于​​应用与跨学科联系​​的章节将展示这些原理在实践中如何应用,从而改变了从科学模拟、数据科学到机器人学和操作系统设计等领域,揭示了驱动我们现代数字世界的众核交响乐。

原理与机制

想象一下你正在建造一台超级计算机。几十年来,方法很简单:只需等待。大约每隔一年,工程师们就会给你一块新的处理器芯片,它更小、更快,而且奇迹般地消耗着大致相同的功率。这个神奇的趋势,被称为​​登纳德缩放(Dennard scaling)​​,是计算世界的“免费午餐”。性能就这么白白地变好了。但大约在21世纪头十年中期,这顿午餐结束了。要理解其中的原因,并 appreciating the birth of the multi-core era, 我们必须审视计算机芯片的基本物理学。

免费午餐的终结:功率墙与暗硅

现代处理器是一个由数十亿个称为晶体管的微观开关组成的繁华城市。每当一个晶体管开关一次,它就会消耗一小股能量。这是它的​​动态功耗​​。你让芯片运行得越快(即其时钟频率 fff 越高),它每秒开关的次数就越多,消耗的功率也越大。这种关系甚至比这更剧烈。为了使晶体管在更高频率下可靠地开关,你还需要增加它们的供电电压 VVV。动态功耗最终与 V2fV^2 fV2f 成正比。很长一段时间里,当我们把晶体管做得更小时,我们也可以降低电压,这是一个绝妙的技巧,它使功耗得以控制。

但这个技巧有其极限。低于某个最小电压 VminV_{min}Vmin​,晶体管会变得不可靠,就像电池快没电的手电筒。我们遇到了电压下限。现在,获得更高速度的唯一方法是提高频率,但由于电压被卡住,功耗急剧上升。这就是臭名昭著的​​功率墙​​。芯片开始变得如此之热,以至于有熔化的风险。那个单核、速度不断提升的时代结束了。

如果我们不能让一个核心更快,我们能做什么?答案很简单,但它将永远改变计算:如果你不能制造一个更快的引擎,那就制造更多的引擎。设计师们不再追求一个极其快速的核心,而是开始在单个芯片上放置多个(通常更简单、更慢的)核心。

然而,这引出了一个有趣的新问题。尽管我们可以在芯片上物理集成数十亿个晶体管,但我们没有足够的功率预算来同时开启所有晶体管,尤其是在全速运行时。这就产生了​​暗硅​​(dark silicon)现象:为了保持在热量限制内,芯片的大部分面积在任何给定时间都必须保持断电,或称“暗”的状态。

这个物理限制带来了一个深刻的选择。想象一下,你有一个拥有160个核心的芯片,功率上限为95瓦。如果一个核心以其最低稳定电压和频率运行时消耗0.595瓦,你无法同时为所有160个核心供电——这将需要 160×0.595=95.2160 \times 0.595 = 95.2160×0.595=95.2 瓦。至少有一个核心必须保持暗态。这不仅仅是硬件设计师的头痛问题;它也是计算机操作系统面临的一个动态难题。对于给定的任务,是更节能地将工作“整合”到少数高速运行的核心上,快速完成然后让核心空闲?还是最好将工作“分散”到许多核心上,每个核心都以非常低、省电的频率运行?

事实证明,答案取决于另一种功耗:​​漏电功耗​​。这是晶体管仅仅因为通电而泄漏的能量,即使它们没有在主动开关。如果漏电功耗很高,你会希望尽快完成工作并完全关闭核心——倾向于整合策略。如果漏电功耗很低,那么通过在许多核心上以更低的电压和频率运行所节省的能量将占优——倾向于分散策略。进入多核世界的旅程就始于这种基本的权衡,这是功耗物理学的直接后果。

瓶颈:并行加速的限制

所以,我们用一个单一的高性能核心换来了一队更小、更高效的核心。如果我们有一个程序和16个核心,我们能期望它运行速度快16倍吗?不幸的是,答案几乎总是否定的。这就是​​阿姆达尔定律(Amdahl's Law)​​的残酷教训。

计算机架构先驱 Gene Amdahl 指出,任何任务的总加速比受限于任务中无法并行化的部分。即使你的程序有10%是内在串行的——一条所有计算部分都必须通过的单行队列——那么即使有无限数量的处理器,你也永远无法获得超过10倍的加速比。串行部分成了最终的瓶颈。

但现实世界的情况更加微妙和有趣。还记得功率墙吗?开启更多的核心会产生更多的热量,系统通常会通过降低所有核心的时钟频率来补偿。让我们想象一个场景,时钟频率随活动核心数 NNN 的平方根而降低,即 f(N)=f0/Nf(N) = f_0 / \sqrt{N}f(N)=f0​/N​。现在我们面临一个有趣的权衡。随着我们增加更多的核心,程序的并行部分会加速。但与此同时,串行部分——它只能在一个核心上运行——实际上变慢了,因为它的核心现在以更低的频率运行!

这导出了一个惊人的结论:对于任何给定的具有串行部分的程序,存在一个最佳核心数。超过这个点,增加更多的核心实际上会使程序运行得更慢。例如,对于一个10%串行(s=0.1s=0.1s=0.1)的程序,在这个模型中最佳核心数仅为 (1−0.1)/0.1=9(1-0.1)/0.1 = 9(1−0.1)/0.1=9。试图在16或32个核心上运行它效率会更低。免费午餐不仅结束了;我们现在还必须非常小心地决定要在餐桌上放多少盘子。

此外,并非所有的“核心”都是生而平等的。术语“多核”越来越多地指代​​异构系统​​,即在单个芯片上包含不同类型的处理器的系统。我们可以使用​​弗林分类法(Flynn's Taxonomy)​​来对它们进行分类。传统的CPU核心是​​MIMD​​(多指令,多数据)引擎;它的每个核心都可以运行一个完全独立的程序。而图形处理单元(GPU)则是一种​​SIMD​​(单指令,多数据)的巨兽。它就像一个军士长,指挥着一个庞大的简单士兵排,让他们都对自己的那份数据做同样的事情(指令)。这对于像图形渲染或科学模拟这样的任务来说是极其高效的。其他专用处理器,如​​SISD​​(单指令,单数据)数字信号处理器(DSP),可能被优化用于像音频处理这样的狭窄任务集。你智能手机中的现代片上系统(SoC)就是一个完美的例子,它通过协调其CPU、GPU和其他加速器之间的任务流水线来高效地执行复杂功能。

处理器议会:通信的挑战

在芯片上拥有许多核心就像召开一个委员会。你可以把最聪明的人都聚集在一个房间里,但如果他们不能有效沟通并就共同的现实达成一致,他们就毫无用处。对于多核处理器,这一挑战归结为两个基本问题:​​缓存一致性​​(维护一个共享、一致的内存视图)和​​同步​​(协调行动)。

让每个人意见统一:缓存一致性

为了避免每次操作都缓慢地访问主内存,每个核心都有自己小而快的内存,称为​​缓存​​。把它想象成每个委员会成员的个人笔记本。问题出现在当核心A在其笔记本中写入一个变量的新值,比如 x=5x=5x=5。核心B有一个旧的笔记说 x=3x=3x=3,它如何知道自己的信息现在已经过时了?

这就是​​缓存一致性问题​​。最常见的解决方案是一个优雅的协议,其缩写很上口:​​MESI​​。缓存中的每一行都标有四种状态之一:​​M​​odified(已修改,这是唯一的副本,且已被更改)、​​E​​xclusive(独占,这是唯一的副本,但内容是干净的)、​​S​​hared(共享,其他核心可能有副本)或​​I​​nvalid(无效,此副本已过时)。这些状态就像一个分布式的图书馆借阅系统。在你写入一本“书”(缓存行)之前,你必须广播一个请求以获得独占所有权,从而使其他所有人的副本失效。

虽然这个协议很巧妙,但它也有其阴暗面。考虑一种常见的同步方法,称为​​自旋锁​​,其中核心反复尝试获取一个“锁”变量以访问共享资源。如果它们使用简单的“测试并设置”操作(这是一个写操作),那么一个自旋核心的每次失败尝试都会触发一个完整的独占所有权请求。如果你有许多核心在争夺这个锁,包含它的缓存行就会在它们之间疯狂地来回传递,每次传输都会在芯片的互连网络上产生大量的失效消息。这通常被称为“缓存行乒乓效应”。

解决方案是硬件和软件的美妙结合。通过编程让自旋者“退避”——在一次失败的尝试后等待一个随机的、指数级增长的时间——它们就不再猛烈冲击内存系统。失效消息的数量,以及因此浪费的通信流量,急剧下降。这相当于数字世界里拥挤房间中的人们学会了在再次尝试发言前礼貌地停顿一下。

共享数据的微妙之处:伪共享与可扩展同步

一致性问题可能更加隐蔽。如果两个核心正在写入完全不同的变量 varA 和 varB 呢?如果内存分配器恰好将 varA 和 varB 放在相邻位置,它们可能最终位于同一个缓存行上。就硬件而言,它只看到缓存行,而不是单个变量。所以,当核心A写入 varA 时,它会使核心B缓存中的该行失效,尽管核心B只关心 varB。这就是​​伪共享​​,它可能在没有任何明显原因的情况下严重削弱性能。

这个问题与其他硬件特性相互作用。为了处理巨大的流量,现代的末级缓存(LLC)通常被分成多个​​切片​​,并且一个哈希函数将每个内存地址映射到一个“归属切片”。给定缓存行的所有流量都通过其归属切片进行路由。现在,考虑一个有许多伪共享实例的程序。完全有可能,仅仅是偶然,这些高流量、乒乓效应的缓存行中不成比例的一部分会被哈希到同一个切片,从而造成网络热点。解决方案再次在于软件:程序员学会填充他们的数据结构,添加未使用的空间以确保不同线程访问的变量位于不同的缓存行上。

这让我们回到了并行编程的一个核心原则:​​避免通信​​。考虑实现一个简单的共享计数器的任务。一种天真的方法是让所有核心对同一个内存位置使用单一的、原子的​​读取并加(FAA)​​指令。这是一种硬件保证“正确”的方法。一种稍微原始的方法是使用带有​​比较并交换(CAS)​​的软件循环。在有 NNN 个核心的高争用场景中,FAA设计效率要高得多。使用CAS,一个核心的尝试会成功,但这会导致其他 N−1N-1N−1 个核心失败并重试,浪费了 N−1N-1N−1 次到内存系统中序列化点的访问。而使用FAA,每次尝试都是成功的。在这个模型中,硬件辅助的FAA字面上比CAS快 NNN 倍。

但我们甚至可以做得更好。一个真正可扩展的算法会重新设计问题,以完全消除争用热点。与其使用一个共享计数器,不如给每个线程自己的私有计数器。每个线程现在可以增加其本地计数器,而无需通信和延迟。一个主线程可以定期遍历并对这些私有计数器求和,以获得全局总数。这种设计产生的缓存一致性流量比天真的方法少了几个数量级,并且随着你增加更多核心而优美地扩展。

顺序的幻觉:内存一致性

我们已经到达了多核处理器最深刻、最令人费解的方面。我们对时间有一种直观的感觉,即事件以单一、普遍的顺序发生。我们期望我们的计算机遵守这一点。如果我写入位置A,然后写入位置B,那么任何其他看到我写入B的核心肯定也应该能看到我写入A。这个假设被称为​​顺序一致性(Sequential Consistency, SC)​​。而在大多数现代处理器上,这是错误的。

为了实现最高性能,核心不会等待一个写操作缓慢地完成它到主内存的旅程。相反,它将写操作放入一个私有的​​存储缓冲区​​,并立即继续执行下一条指令。这意味着一个核心可以执行程序中位于一个存储指令之后的加载指令,即使该存储指令的值仍停留在缓冲区中,对系统的其余部分不可见。

这可能导致一些看似违背逻辑的结果。考虑两个线程 P0P_0P0​ 和 P1P_1P1​,初始时 xxx 和 yyy 都为零。

  • ​​线程 P0P_0P0​​​: $x := 1$, 然后读取 y.
  • ​​线程 P1P_1P1​​​: $y := 1$, 然后读取 x.

P0P_0P0​ 读取到 y=0y=0y=0 并且 P1P_1P1​ 读取到 x=0x=0x=0 是可能发生的。怎么会这样?P0P_0P0​ 将其对 xxx 的写入放入其存储缓冲区,并在 P1P_1P1​ 的写入到达内存之前从内存中读取 yyy。与此同时,P1P_1P1​ 将其对 yyy 的写入放入其缓冲区,并在 P0P_0P0​ 的写入变为全局可见之前从内存中读取 xxx。每个核心都看到了对方的初始状态,这是一个被顺序一致性所禁止的结果。

这是现代处理器的一项重大交易。它们通过这种​​松散内存模型​​为你提供令人难以置信的性能,但作为回报,程序员(或编译器)承担了告诉硬件何时顺序真正重要的责任。这是通过​​内存栅栏​​指令来完成的。栅栏是代码中的一个屏障,基本上是说:“暂停。在我之前的所有内存操作都变为全局可见之前,不要继续执行。”栅栏是我们用来在一个高速、并行的混乱世界中恢复局部顺序感的明确命令。它们是性能的代价,也是一扇窥见使我们多核世界成为可能的美妙复杂机器的窗口。

众核交响曲:应用与跨学科联系

在上一章中,我们深入探讨了使多核处理器运转的基本原理。我们看到了单个芯片如何容纳多个独立的大脑,即“核心”,并且我们探索了为防止它们相互干扰所需的缓存一致性和同步的精妙舞蹈。但是,了解舞蹈的规则是一回事;编排一出杰作则是另一回事。

拥有多个核心就像得到一个管弦乐队。如果所有音乐家都演奏自己的曲调,你得到的是一片嘈杂。要创作一首交响乐,你需要一份乐谱——一个计划——告诉每个人该做什么以及何时做。你需要一个指挥来引导演出。最重要的是,你需要值得演奏的音乐。本章就是关于那段音乐的。我们将探讨多核处理能力如何在从科学最深层的问题到机器人学和工程学的具体挑战等广泛的学科中得到利用。我们正在从“如何做”转向“为什么做”和“为了什么做”,发现并行计算对我们周围世界的深远影响。

数字宇宙:加速计算本身

从本质上讲,现代科学的大部分是计算科学。从天气预报到新药设计,我们依赖于我们解决极其复杂的数学问题的能力。多核处理器是这场革命的引擎,但释放它们的力量需要的不仅仅是蛮力。它需要对计算本身的“物理学”有深刻的理解。

现代处理器是一头极其饥饿的野兽。它的核心可以以惊人的速度进行计算,但它们常常因数据而“饥饿”,被迫等待信息从主内存中缓慢地穿梭而来。处理器速度和内存速度之间的这个鸿沟通常被称为​​内存墙​​,它是高性能计算中最重要的单一限制因素。

突破这堵墙的关键是一个叫做​​算术强度​​的概念——即执行的计算量与移动的数据量之比。一个具有高算术强度的算法,对它从内存中获取的每一片数据都执行许多操作,使得这次访问物有所值。而一个低算术强度的算法,就像一个木匠开车去木材厂只为了一颗钉子,开回来,把它钉进去,然后为下一颗钉子重复这个过程。这是极其低效的。

这一原则在数值线性代数中表现得最为明显,它是科学模拟的基石。考虑QR分解任务,这是求解方程组的主力。一种经典方法使用一系列“吉文斯旋转”(Givens rotations),即一次只将一个元素归零的小操作。虽然这些旋转数量众多,可以提供许多小的并行任务,但每个任务的算术强度都非常低。它们受内存限制。

一种更聪明的方法,称为​​分块Householder方法​​,将工作分组。它一次性计算矩阵整个面板的一组变换,然后使用高度优化的矩阵-矩阵乘法例程(三级BLAS)将它们一起应用到矩阵的其余部分。这些大的分块操作具有非常高的算术强度,对 O(N2)O(N^2)O(N2) 的数据执行 O(N3)O(N^3)O(N3) 的操作。它们将大块数据带入快速缓存中,对其进行大量处理,然后才将结果写回。这是在多核CPU和大规模并行GPU上获得高性能的秘诀。尊重内存层次结构的算法获胜。

一旦我们有了这些算术强度高的构建块,下一个挑战就是将它们组装起来。像高斯消元这样的算法可以看作是一系列任务的集合——分解一个面板、更新一个块——它们之间存在依赖关系。我们可以将此工作流表示为一个​​有向无环图(DAG)​​,其中节点是任务,边代表一个排序约束:你不能更新一个块,直到它对应的面板已经被分解。这个图的结构揭示了算法的内在并行性。一个“又高又瘦”的图意味着长的依赖链,几乎没有并行执行的机会,而一个“又短又宽”的图则暴露出许多可以并发运行的独立任务。值得注意的是,有时仅仅通过重新排序操作——而不改变最终的数学结果——我们就可以极大地改变这个DAG的形状,并释放巨大的性能增益。

算法画布:重塑经典

多核革命迫使我们重新审视和重构那些在计算机科学入门课程中教授的最基本的算法。为单个顺序思维设计的算法必须被重新教导以并行方式思考。

考虑构建一个二叉堆(binary heap)的任务,这是一种基本的数据结构。标准的教科书算法是从树的底部向上工作,确保每个节点都满足堆属性。一种巧妙的并行化方法是注意到树同一层的所有节点都位于不相交的子树中。因此,给定层级上所有节点的 heapify 操作可以同时执行,每个核心一个任务。我们可以从下到上一层一层地处理这棵树,并在每层之间进行同步。这种“层级同步”方法是一种简单而强大的模式,用于在树状结构上并行化工作。

一个更复杂的挑战出现在对无法放入内存的大规模数据集进行排序时,这个过程称为外部排序。一个关键步骤是​​k路归并​​,其中 kkk 个预先排序的数据块被合并成一个最终的排序输出。你如何并行化这个过程?有人可能会倾向于使用一个单一的、共享的数据结构(如优先队列),所有核心都使用细粒度锁来访问它。这几乎总是一个错误。共享结构成为瓶颈,核心们花在排队上的时间比做有用工作的时间还多。一个更好的策略是以不同方式划分问题。我们可以划分输出,而不是划分输入。我们找到“分割”元素,将最终排序的数组分成 ppp 个大小相等的块。然后每个核心负责为其分配的块生成元素,对输入运行的相关切片执行自己独立的k路归并。这是一种粗粒度的方法,最大限度地减少了通信和同步,并且扩展性极佳。

也许最有趣的例子来自图算法。你如何并行地找到一个庞大网络(如社交网络)的连通分量?顺序搜索似乎是不可避免的。并行的解决方法出人意料地反直觉。我们首先为每个节点分配其自己的ID作为其分量标签。然后,在一系列同步的回合中,每个节点都将其标签更新为自身标签与其所有邻居标签的最小值。这种“标签传播”使得一个分量中最小的节点ID像波浪一样在该分量中泛滥。几轮之后,一个分量中的所有节点都会统一到一个单一的、最小的ID上。这种体同步并行(BSP)模型,即工作在并行的“超步”中完成,超步之间由全局同步分隔,是现代大规模图处理的基石。

总指挥:操作系统

谁来管理所有这些疯狂的并行活动?谁将任务分配给核心,确保数据到达需要去的地方,并保持一切顺利运行?这是​​操作系统(OS)​​的艰巨任务,它是我们多核管弦乐队的总指挥。为了有效,操作系统内核本身必须是高度并发的,但这打开了一个充满微妙和危险挑战的潘多拉魔盒。

想象一下内核内部的一条“快速路径”,旨在通过使用专用的、每核一个的缓冲区来避免数据复制。这似乎是一个很好的优化。但是在具有​​弱内存模型​​的现代处理器上会发生什么?处理器被允许为了性能而重排内存操作。它可能会让“数据就绪”标志在实际数据写入缓冲区之前对同一核心上的消费者可见!消费者读到了垃圾数据,系统崩溃了。如果发生中断,并且中断处理程序也需要使用同一个每核缓冲区怎么办?这两个调用会破坏彼此的数据。

解决这些问题需要精心的工程设计。为了强制排序,我们必须使用显式的​​内存栅栏​​(或获取-释放语义),它告诉处理器:“不要跨越此点重排操作。”为了处理嵌套调用,单个缓冲区是不够的;我们需要一个包含多个槽位的每核环形缓冲区。为了防止一个线程在操作中途被迁移到另一个核心,我们必须短暂地禁用抢占。构建一个正确、高性能的内核就是在这种并发危险的雷区中航行,在这里,机器的架构和软件的逻辑密不可分地联系在一起。

除了正确性,操作系统也是最终的调度器。给定一组具有复杂依赖关系、内存需求和截止时间的任务,操作系统必须决定哪个任务在哪个核心上何时运行,以优化某个目标,例如最小化总执行时间(“完工时间”)。这是一个臭名昭著的难题,通常是NP-hard的。远非简单的先来先服务队列,现代调度可以涉及复杂的数学技术。该问题可以建模为一个​​凸优化程序​​,这是一种来自数值方法的强大工具,用于找到一个可证明是好的(如果不是绝对最优)的调度方案,该方案尊重系统中所有复杂的约束。

连接物理世界:从模拟到控制

多核处理的影响远远超出了数字领域,使我们能够以前所未有的保真度和速度模拟和控制物理世界。

在计算化学中,科学家使用分子动力学(MD)来模拟原子和分子的舞蹈。这些模拟需要强制执行物理约束,例如保持原子间的键长固定。​​SHAKE算法​​就是执行此操作的迭代过程。为了并行化SHAKE,我们面临一个熟悉的问题:如果两个约束共享一个原子,它们是相互依赖的,不能同时处理。优雅的解决方案直接来自图论。我们可以建立一个“约束图”并找到一种​​图着色​​方案,其中每种颜色代表一组彼此独立的约束。然后并行算法一次性处理所有单一颜色的约束,同步,然后移动到下一种颜色。这是物理学、计算机科学和数学的美妙结合,以揭开物质的秘密。

在另一个极端是物理系统的控制,例如机器人手臂。在这里,正确性和及时性不仅是可取的;它们关乎安全。考虑一个系统,其中多个工作线程控制手臂的运动,使用自旋锁来保护共享状态。一个具有最高优先级的紧急停止线程必须能够在严格的截止时间内停止手臂。如果紧急信号在一个低优先级的工作线程刚刚获取了锁,并且为了确保其操作的原子性而禁用了抢占之后到达,会发生什么?在单核系统上,高优先级的紧急线程无法运行,尽管它是系统中最重要的任务。它被低优先级的线程阻塞了。这种危险的情况称为​​优先级反转​​。紧急停止的最坏情况延迟不仅是其自身的执行时间,而且是工作线程持有锁的最长时间加上其自身的执行时间。如果这个总和超过了安全截止时间,那么这个设计就是不安全的。这说明了实时和安全关键系统的一个关键教训:可预测的性能通常比最佳的平均情况性能更重要。

机器中的科学家

在这次应用巡礼之后,还剩下一个最后的问题:我们如何知道我们巧妙的算法和系统是真的受限于核心、受限于内存,还是受限于其他什么东西?我们如何诊断性能?答案是,我们成为科学家。

现代处理器配备了​​性能监控单元(PMUs)​​,它们就像一套庞大的仪器,用于观察机器的内部工作。它们可以计数从完成的指令到缓存未命中再到传输的内存字节数的一切。通过设计仔细的实验,我们可以使用这些计数器来检验关于性能的假设。

为了确定一个程序是受核心限制还是受内存限制,我们可以进行两个关键实验。首先,我们改变核心频率,同时保持内存系统不变。如果程序的吞吐量与频率成线性关系,那么它很可能是受核心限制的。如果它的性能几乎没有变化,那么核心很可能只是在等待内存。其次,我们固定核心频率,并引入一个消耗内存带宽的“内存大户”后台任务。如果我们的程序的性能显著下降,这清楚地表明它在竞争内存带宽,因此是受内存限制的。

这种经验性的、科学的方法使我们回到了起点。驾驭多核处理器的旅程不仅仅是一种巧妙的编程行为。这是一项科学事业,要求我们理解计算的基本物理学,设计尊重这些 법칙 的算法,构建能够管理巨大复杂性的系统,并将它们应用于解决世界上的实际问题——所有这些同时,像任何优秀的科学家一样,不断地测量、测试和完善我们的理解。