2002 年,DARPA(国防高级研究计划局)启动了一项 HPCS(高生产力计算系统)的重大计划。该计划的动机是相信,即将到来的并行机器的利用受限于在 peta 规模上编写、调试、调整和维护软件的难度。
作为这项计划的一部分,DARPA 鼓励对新的编程语言、运行时和工具进行研究。它认为,通过使并行构造的表达更容易,使运行时模型与正在开发的异构处理器架构相匹配,并提供强大的集成开发工具,可能会提高程序员的生产力。这是一个合理的推测,但我们试图超越推测,进行实际的生产力提升测量。
虽然没有既定的方法来衡量程序员的生产力,但很明显,生产力指标必须采取比率的形式:获得的编程结果与获得这些结果的成本之比。在本例中,结果被定义为成功创建一组在两个工作站核心上正确运行的并行程序。这与 peta 规模相去甚远,但由于新的并行软件通常以此方式开始(然后在越来越多的处理器上进行扩展和调整),我们认为这是一个合理的近似值。此外,使用两个核心发现的结果应该对那些编写几乎任何并行应用程序(无论多么小)的人感兴趣。成本一旦定义了结果,就更容易处理,因为它可以用创建这组并行程序所花费的时间来合理地近似。
本研究的目的是衡量程序员的生产力,以此定义,从 2002 年 HPCS 计划开始的几年内。比较主要集中在两种并行编程方法上:SPMD(单程序多数据)模型,以 C/MPI(消息传递接口)为例;以及 APGAS(异步分区全局地址空间)模型,由 X10 等新语言支持 (http://x10-lang.org),尽管环境和工具的差异也进行了研究。请注意,比较不是现在已经存在的 C/MPI 和现在的 X10 之间的比较。相反,这是一个对 2002 年的情况与现在情况的历史对比。实际上,带有异常的 C++ 和带有单边通信协议的 MPI-2 可能会提高程序员的生产力,并且值得单独研究。
鉴于我们的目标,我们力求尽可能地复制 2002 年 C/MPI 用户的编程环境。这包括 gdb 调试器,以及一组典型的命令行工具。对于 X10,我们使用了 Eclipse (http://www.eclipse.org) 和 X10 插件,就像 2010 年(本研究的日期)发现的那样。X10 是作为 HPCS 计划的一部分开发的。它结合了 Scala 等较新语言的简洁性 (https://scala-lang.org.cn) 和并发编程模型,该模型很好地映射到现代并行机器的 PGAS 模型。十年后,该语言仍在发展,但当这项研究开始时,基本模型已经稳定。重要的是,在本次研究完成之前,没有可用的 X10 调试器,因此当前 X10 用户可以预期看到比此处报告的更大的收益。
这不是第一个尝试衡量 X10 编程生产力的研究。Ebcioglu et al.5 和 Danis 和 Halverson3 描述了一个实验,其中本科生(并行编程新手)接受了 C/MPI、UPC(统一并行 C)或(非常早期的版本)X10 的培训,然后尝试并行化 SSCA 1(可扩展合成紧凑应用程序 1)的字符串匹配算法,如 Bader et al.2 中所述。C/MPI 的平均完成时间约为 510 分钟,UPC 为 550 分钟,X10 为 290 分钟。当消除测试期间代码执行时间的差异时,这些时间对于 C/MPI 约为 360 分钟,UPC 为 390 分钟,X10 为 180 分钟。然而,对这种大约两倍的生产力提升的解释有些复杂,因为代码完成没有明确的成功标准。
在随后的研究中,Halverson 和 Danis6 将 Eclipse 编程环境添加到 X10 程序员可用的工具中。本研究的参与者更有经验,他们在之前的课程作业中接受过一些并行编程培训。此处也发现了 X10 和 Eclipse 相对于 C/MPI 的生产力提升,但计算这种提升的大小很复杂,因为七名 C/MPI 参与者中只有一名完成了任务(耗时 407 分钟),而七名 X10 参与者中有五名完成了任务,平均耗时 321 分钟。
在之前的这两项研究中,只编写了一个程序(SSCA 1 的一部分),只测试了相对新手的并行程序员,并且任务仅限于并行化一个易于并行化的算法,该算法以可运行的串行代码形式提供给测试参与者。此处报告的评估解决了所有这三个缺陷。
在一项长期运行的研究中,我们测量了用两种语言开发六个并行程序的时间,总共 12 个程序。每个程序都是根据问题陈述和/或算法描述开发的,没有利用任何预先存在的代码示例。完成时间以首次使用双核工作站成功并行运行结束
六个程序中的两个在 DARPA HPCS 计划的第 2 阶段中定义,即 SSCA 1 和 SSCA 2,并在 Bader et al.2 中进行了描述。SSCA 1 是先前两项研究中使用的字符串匹配问题。然而,在本研究中,每种语言都开发了两个版本的 SSCA 1:当目标字符串比搜索字符串短得多时效果良好的易于并行化的版本,以及当两个字符串长度大致相等时更合适的更复杂的反对角线版本。SSCA 2 是本次评估的新内容,每种语言都编码了所有四个内核。
除了这两个 SSCA 之外,还定义了另外四个问题,基于对伯克利模式的分析。1 所有六个问题都在下一节中描述。
开发这 12 个程序花了将近一年时间。如果有多个熟练的程序员执行这项任务固然是理想的,但这将大大超出可用资金。相反,研究成本通过仅让一位程序员(我们的生产力评估团队的成员,也是本报告的作者之一)担任测试参与者来控制。他是一位接受过博士学位的数学家,自 1979 年以来一直从事 C 编程专业,编写了 IBM 第一个 C 编译器的前端,并且自 2007 年以来一直在为多核系统编写 MPI。他也在 2007 年开始编写 X10,开发了 Halverson 和 Danis6 研究测试平台,并编写了 X10 教程的几个部分。虽然将单个程序员的结果视为轶事很诱人,但观察到的生产力提升在新手程序员在更简单任务上发现的范围内,并且考虑到程序员在 C 方面的更高熟练程度以及研究期间缺少 X10 调试器,这可能是相当保守的。
本研究定义了六个编程问题,每个问题代表一类并行应用程序。它们共同涵盖了典型的中小型并行程序的相当大的范围
• SSCA 1(第一个内核)。这涉及一个字符串匹配问题,其动机来自染色体和蛋白质研究。要求找到一对大写字母字符串的子字符串之间最佳的近似匹配。
• SSCA 2(所有四个内核)。这个问题涉及稀疏图的有效表示,已知该图的边集先验地随机分布在可用处理器的集合中。第一个内核构建图,格式将用作所有后续内核的输入。第二个内核从图表示中按权重提取边,并形成权重最大的边列表。第三个内核提取一系列子图,这些子图是通过从一组初始顶点开始沿着指定长度的路径形成的。最后一个内核计算网络中心性指标,该指标识别图中最短路径上的关键重要顶点。
• 生产者-消费者。这个问题使用一个进程作为服务器,管理多个客户端进程的队列,这些客户端进程随机地向队列添加和从中删除。由于一个缓冲区是所有操作的目标,因此第一个编程问题是最小化争用。由于向队列添加元素和从队列中删除元素发生在相反的末端,因此这是避免争用的主要机会;如果队列足够大,您可以安全地同时执行这两个操作。
• 不平衡树搜索。与前一个问题中通过中央服务器进程管理协调不同,这里一组对等进程各自管理自己的工作队列,并且在需要时与其他进程共享工作。总体任务是对已知叶子与根的深度差异很大的树进行广度优先搜索。每个处理器管理自己的未访问树节点队列,但可能会被空闲对等方要求提供一些节点供其处理。
• Floyd 算法。给定一个有向图,其边带有权重,要么是无环的,要么只有非负权重,我们计算所有顶点对之间最短路径的权重之和。
• 离散傅里叶变换。实现的算法是最初的 Cooley-Tukey 快速傅里叶变换。输入是一个 2n 个复数的数组,输出也是如此。至少在 n=3 和 2n=8 之前,传统的串行算法击败了 Cooley-Tukey,这意味着您可以有利地将数组分布在 2n/8 个进程中。
表 1 总结了首次成功并行运行所需的天数以及为每个 12 个程序编写的代码行数。X10 中的六个程序总共需要 39 天才能开发完成。使用 MPI 的 C 语言编写的六个程序总共需要 129 天才能开发完成。因此,由于语言(以及次要的环境)带来的总体生产力提升超过 3 倍。在编写 X10 的 39 天中,编写了 6,195 行代码,平均每天 159 行代码。在使用 MPI 的 C 语言编写的 129 天中,编写了 10,245 行代码,平均每天 79 行代码。虽然我们没有测量这些程序的性能,但 Saraswat et al.7 的一项研究考察了 X10 实现的不平衡树搜索程序的出色并行性能,该程序与我们的程序非常相似。
本研究中发现的三倍生产力提升是什么原因造成的?虽然部分提升是 Eclipse 和 X10 插件提供的集成工具的结果,但大部分归因于 X10 语言的特性。这些特性包括 activity
和 place
提供的任务和数据分区,async
和 finish
提供的灵活任务支持,以及良好集成的异常处理机制。结合起来,这些简化了并行性的表达,并使程序结构更紧密地反映了自然问题结构。此外,自动垃圾回收消除了管理内存的繁琐工作,而面向对象简化了处理复杂结构的任务,同时避免了内存引用的误算。以下各节提供了这些特性如何提高生产力的示例。
X10 程序从在单个位置执行的单个活动开始。活动是执行的单个顺序线程,位置是将处理能力与分区全局内存共享相结合的系统的一部分。任何活动都可以在需要时在其当前运行的位置或任何其他方便的位置启动新活动。
本地线程。作为一个简单的例子,SSCA 1 需要从某个外围来源读取一对字符串。如果字符串非常长(因为这个问题旨在扩展到任意长的字符串,所以很可能如此),那么最好同时读取这两个字符串。如果两个流都将从单个处理器访问,则图 1 显示了您需要的 X10 代码。
简单的 fork-join 并行性在 X10 中使用 async
语句来完成 fork,并用 finish
块包围活动集以实现 join。包围整个计算的 try
块捕获可能发生在文件 I/O 或活动启动/关闭中的任何错误。
对于 C 语言,本质问题是如何获得第二个线程,原始线程可以与之共享内存。图 2 显示了使用 POSIX 线程在 C 语言中完成的相同代码。
SPMD 模型相对较差的适应性体现在 my_id==0
的测试中,这是一个典型的 C/MPI 习惯用法。如果发生 I/O 错误,处理器 0 必须让其他处理器知道有问题。这就是为什么在读取字符串后必须进行广播,无论是否有任何问题。函数 do_the_read
封装了第二个线程的任务。即使它正在执行与根线程完全相同的读取,它也必须是独立的。
远程线程。前一个例子非常简单,很容易低估其重要性。为了更好地理解,请考虑一个稍微的变化。假设,数据所在的处理器不是已知由处理器 0 处理大部分工作,而是仅在运行时才知道,就像数据可能来自网络或一组数据库一样。代码如何更改?图 3 显示了 X10 代码。
X10 程序中的位置,如 MPI 进程,具有唯一的整数 ID。在图 3 中,两个序列正在从 ID 为 seq1Id
和 seq2Id
的位置读取。读取操作与原始操作的不同之处仅在于必须提供 at
子句来说明每个读取将在哪里发生。所有剩余代码都保持不变,包括错误处理,即使现在可能涉及多个处理器。这是 X10 异常模型的结果,该模型旨在反映程序的活动树。它允许该树中的任何活动处理由其下方子树中的任何内容抛出的异常。(X10 异常不会自动包含抛出异常的位置,尽管程序员可以轻松地子类化 exception
并插入位置信息。此外,异常对发生异常的活动的子活动或同级活动没有影响。)虽然 C++ 在 2002 年之前为串行代码引入了 try
/catch
样式的异常处理,但没有一个线程框架将其移植到多进程集,其简洁性与 X10 相同,在 X10 中,单线程和多线程情况都可以在不更改的情况下得到支持。
访问序列化:单服务器队列是一种常见的模式,其中一组参与者对项目流的串行写入由可能不同的另一组参与者读取。通常的解决方案是将项目缓冲在数组中,直到它们被消费。项目添加到数组的末尾,但从其开头删除。
对于多个参与者,这种情况下的竞争条件的可能性是显而易见的。来自数组的 Remove
操作必须彼此序列化,adds
也必须如此。另一方面,除非剩余的项目很少,否则 add
和 remove
不需要彼此序列化,因为它们影响数组的相反两端。
当安全时,如何通过允许 add
与 remove
并行来榨干最后一点性能?序列化两个 adds
或两个 removes
只需要 C 的后缀 ++ 的轻量级原子版本。当剩余的项目很少时,所有操作都必须序列化,这需要一个原子代码块来保证线程一旦在块中处于活动状态,它将是块中唯一的线程,直到它退出块。
在 X10 中,类 AtomicInteger
提供对单个整数值变量的原子更新。由于这使用了硬件支持,因此它是一种比保护整个块更轻量级的操作。关键字 atomic 在语句块前面对块的访问进行序列化。因此,X10 提供了编写 650 行代码所需的内容,这些代码读取起来非常自然。截至 2002 年,没有类似于 AtomicInteger
或原子块的标准化 C API。(参见 http://gcc.gnu.org/onlinedocs/gcc-4.1.1/gcc/Atomic-Builtins.html。此 API 本质上是在 C11 中标准化的内容。)您从原子块和原子更新中需要的序列化可以通过使用同步 MPI 调用(如 MPI_Recv
)来实现,但生成的代码更长(在我们的实现中为 1,100 行),并且更难理解。
对于那些想知道为什么需要 1,100 行代码的人来说,只有非常小一部分代码支持队列操作。这是一个客户端-服务器应用程序。服务器必须启动,它必须停止,客户端必须发出请求,必须有访问控制(不是每个人都可以使用每个数组),等等。X10 版本如此短的原因是更简单的错误处理,并且不必编写客户端和服务器 API。
对等协调。在前一个示例中,协调是通过中央服务器进程管理的。当一组对等进程各自管理自己的队列,并且在需要时与其他进程共享项目时会发生什么?这里,每个进程都充当某些活动进程子集的客户端和服务器。我们编程的示例对已知事先存在大量不平衡的树执行广度优先搜索。每个处理器管理自己的未访问节点队列,但是,为了更均匀地分配工作负载,可能会要求处理器将一些未处理的节点发送到另一个处理器进行处理。
这里最大的问题是终止:进程如何知道不仅自己没有工作了,而且所有其他进程也没有工作了?处理这个问题的算法(至少在较低的规模上)可以追溯到大约 30 年前的 Dijkstra et al.4,并且此后已被多次改进。SPMD 解决方案基于它们。X10 解决方案完全不同,它依赖于按需线程。
让我们首先看看 X10。单个 X10 活动通过要求集合中的每个对等方开始搜索树的一部分来开始操作。如果其中一个对等方用完了工作,它会有一个“附近”对等方的列表,它被允许向其请求更多工作。它通过将对自身的引用发送给它的每个邻居来实现这一点,然后让它当前的活动死亡。但是,发送这些请求的对象继续存在。因此,如果一个空闲对等方的邻居有空闲工作,它可以在空闲对等方的位置生成一个新的活动,在该活动中,该对等方可以恢复工作。如果对等方想要确保只接受一个邻居的工作,则需要进行一些握手,否则这就是整个故事。由于根活动已将多个对等活动生成为单个 finish
块内的 asyncs
,因此当这些 asyncs
中没有一个保持活动状态时,它可以简单地退出该块,从而完成作业。
C 解决方案完全不同。每个处理器上都没有准备好接收远程进程调用的对等对象,即使有,也没有 X10 finish
块的类似物。处理空闲对等方的幼稚方法是让它挂起在同步接收上,但是谁负责知道何时向该对等方发送“全部完成”消息?这正是 Dijkstra et al. 通过一种巧妙的轮询所有对等方的方法正在解决的问题。
在 C 解决方案中,每个处理器必须执行三个活动:主要工作(即,搜索和处理其共享的树);侦听终止控制消息;以及侦听其邻居的共享工作请求。通过使用两个非阻塞接收来侦听控制和共享通信,每个处理器实际上有三个并发活动:主要活动和两个挂起的接收。每个处理器的主循环负责及时处理传入的控制和共享请求。大部分复杂性都涉及决定谁向谁发送什么控制信息。
上一节讨论的问题与我们的程序的自然线程模型与 X10 APGAS 和 MPI SPMD 并行模型之间的契合度有关。还有几个众所周知的与 C 相关的缺点,这些缺点影响了我们编写串行代码部分的生产力。这些缺点在 X10 中不存在。平均而言,我们在每 1,000 行 C 代码中遇到了大约六个这些众所周知的问题,总体影响很大。许多问题需要额外的努力,因为它们并没有在需要更正的点附近显现出来。
内存泄漏。X10 像许多最近的语言一样,具有自动垃圾回收。长期以来,显式管理存储的需求一直被认为是 C 编码生产力的严重障碍之一。您可能会认为,在一个像 SSCA 1 这样简单的程序中,大约有 1,200 行相对简单的字符串匹配代码,这不会是一个问题——但事实并非如此,尤其是当代码要长时间运行时(或要合并到经常调用的库例程中时)。在这里,需要查找和消除内存泄漏,对于在我们代码中发现的内存已分配但从未释放的位置,这项任务很容易花费一两天的时间。
正确获取内存引用。C 语言中缺少内省也是一个重要问题。在像 X10 这样的面向对象语言中,对象知道它们有多大以及存在哪些未完成的引用。在 C 语言中,这些计算必须手动完成。并非使用 X10,您就不能错误地计算数组范围。仍然有可能,但 X10 会在运行时在访问点捕获对数组的越界访问。即使是熟练的 C 程序员也不太可能花时间来为每个数组访问进行防错处理。由于错误的访问通常在远离需要更正代码的点被检测到,因此这些错误不容易被发现和修复。
错误处理。C 语言中缺少方便的异常机制迫使程序员更加冗长。例如,在 Floyd 算法中,当我们的程序员想要一个通用输入流来读取 ASCII 数字值流时,这一点就显现出来了。他的 API 有一个条目,可以对流进行标记化,将标记转换为适当的数字类型,并确保该值是合法的。显然,流可能会遇到许多问题。问题是如何处理这些错误。
在 X10 中发生错误时,会抛出一个异常,其类型标识遇到的问题。应用程序可以避免为检测通用错误(如下层函数发现的意外文件结尾)提供特殊代码,因为它们也可以通过抛出异常来发出错误信号。因此,应用程序可以专注于在内容中发出语义错误信号。
对于大多数一次性代码,错误处理不是一个严重的问题,但对于生产代码,以及对于复杂的通信模式(如 Dijkstra et al. 的终止算法),它肯定是。C 语言的信号机制最适合专家使用。然而,C 语言的问题在多线程 SPMD 世界中甚至更加深入。考虑标准库例程 strtoll
,流调用该例程将找到的标记转换为长整数。以下是 strtoll
的错误指示的讨论,如其“man”页所示
“strtol
、strtoll
... 函数返回转换的结果,除非该值会下溢或溢出。如果无法执行任何转换,则返回 0,并将全局变量errno
设置为EINVAL
(最后一个特性并非在所有平台上都可移植)。如果发生溢出或下溢,则将errno
设置为ERANGE
...”
考虑 C 应用程序需要哪些代码才能处理各种可能的错误。代码是否应该确保在调用 strtoll 之前将 errno
置零?毕竟,它可能是非零的,因为完全不相关的早期问题。对于检查 errno
以了解发生情况的代码,此外,仅仅检查 errno
是否为非零是不够的,因为错误可能是 I/O 错误、解析错误或范围错误。您也不能确定 errno
是否是线程安全的——它并非在所有系统上都是线程安全的。那该怎么办?以及您应该在应用程序的哪个位置清除 errno
,其值是全局的?哪些其他进程需要了解问题,以及应该如何让他们知道?
C 和 MPI 在并行编程社区中占据主导地位有很多很好的理由。它们是经过精心记录、优雅、干净的设计,并且经过了仔细的实现。在经验丰富、纪律严明的专业人士手中,它们提供了其他地方根本无法获得的控制级别。C 和 MPI 都不是静止的目标:两者都在不断改进,即使它们现在是成熟的技术。
然而,C/MPI 的优势在多大程度上超过了它的成本?通过我们所有的三项研究,尤其是在最后一项研究中,我们看到了使用更高级别的语言和编程模型带来的巨大好处——从首次成功并行运行的开发速度快两到六倍不等。对于需要维护多年的大型程序,这些生产力优势甚至可能更大。
X10 及其 APGAS 编程模型现在正在许多研究和大学环境中探索。虽然该语言可能不会跨入主流使用,但使其对我们来说效率如此之高的品质很可能在并行社区中得到确立:灵活的线程、自动垃圾回收、运行时类型驱动的错误检查、分区全局内存和基于根的异常处理都很有价值。我们希望我们的实验能够鼓励那些希望提高并行程序员生产力的人认真研究 X10 的设计及其优势。
这项工作得到了国防高级研究计划局在其协议编号 HR0011-07-9-0002 下的支持。作者感谢 IBM HPCS 生产力评估团队的成员 Catalina Danis、Peter Malkin 和 John Thomas 对这项研究的宝贵贡献。
1. Asanovic, K., Bodik, R., Catanzaro, B. C., Gebis, J. J., Husbands, P., Keutzer, K., Patterson, D. A., Plishker, W. L., Shalf, J., Williams, S. QW, Yelick, K. A. 2006. 并行计算研究的格局:来自伯克利的观点。技术报告编号 UCB/EECS-2006-183。加州大学伯克利分校电气工程与计算机科学系; http://www.eecs.berkeley.edu/Pubs/TechRpts/2006/EECS-2006-183.pdf。
2. Bader, D. A., Madduri, K., Gilbert, J. R., Shah, V., Kepner, J., Meuse, T., Krishnamurthy, A. 2006. 为基准测试高生产力计算系统设计可扩展的合成紧凑应用程序。CTWatch Quarterly 2(4B); http://www.cse.psu.edu/~madduri/papers/SSCA-CTWatch06.pdf。
3. Danis, C., Halverson, C. 2006. 从综合方法中观察性组件获得的价值,用于研究 HPC 程序员生产力。在第三届高性能计算生产力和性能研讨会论文集: 11-21。
4. Dijkstra, E. W., Feijen, W. H. J., van Gasteren, A. J. M. 1983. 分布式计算终止检测算法的推导。信息处理快报 16 (5): 217-219。
5. Ebcioglu, K., Sarkar, V., El-Ghazawi, T., Urbanic, J. 2006. 衡量三种并行编程语言生产力的实验。在第三届高性能计算生产力和性能研讨会论文集: 30-36。
6. Halverson, C. A., Swart, C., Brezin, J., Richards, J., Danis, C. 2008. 面向科学计算的程序员行为的生态有效性研究。在第一届计算科学与工程软件工程研讨会论文集中。
7. Saraswat, V. A., Kambadur, P., Kodali, S., Grove, D., Krishnamoorthy, S. 2011. 基于生命线的全局负载均衡。第 16 届 并行编程原理与实践研讨会论文集:201-212。
喜欢它,讨厌它? 让我们知道
John Richards 是 IBM Watson 集团的研究经理,并被任命为苏格兰邓迪大学计算机学院的名誉教授。他获得了人因素学会颁发的 Alexander C. Williams Jr. 奖,并且是计算机协会院士、IBM 技术学院成员和英国计算机学会院士。
Jonathan Brezin 接受过数学家培训,并在 20 世纪 60 年代和 70 年代在明尼苏达大学和北卡罗来纳大学担任职位,最终于 70 年代中期成为教堂山 UNC 的数学教授。在贝尔实验室和华尔街短暂工作后,他加入了 IBM 研究院。最初,他致力于优化编译器,但最近他转向支持各种可访问性计划。这项工作标志着他回归初恋和他的绝唱——他今年从 IBM 退休。
Cal Swart 于 1982 年加入 IBM 研究院,目前是 IBM Watson 集团的高级技术人员。他毕业于 Calvin College,并拥有数学和物理学学位。在他的 IBM 职业生涯中,他从事过许多项目,涉及程序员工具包、增强的 Web 体验、可访问性、生产力和手持应用程序。他是 Watson Life Research 的成员,探索认知计算的新应用。
Christine Halverson 是硅谷的独立顾问。她以前在 IBM 研究院工作,在那里她花了五年时间在 DARPA HPCS 计划中研究并行程序员。其他兴趣包括 CSCW(计算机支持的协同工作)、HCI(人机交互)以及语音技术在各种环境中的应用。
© 2014 1542-7730/14/0900 $10.00
最初发表于 Queue vol. 12, no. 9—
在 数字图书馆 中评论本文
Adam Morrison - 多核程序中的可扩展同步
为现代多核处理器设计软件提出了一个难题。传统的软件设计中,线程操作共享数据,其可扩展性有限,因为对共享数据更新的同步会串行化线程并限制并行性。替代的分布式软件设计中,线程不共享可变数据,消除了同步并提供了更好的可扩展性。但是分布式设计使得实现共享数据结构自然提供的特性(例如动态负载均衡和强一致性保证)具有挑战性,并且根本不适合每个程序。然而,通常,共享可变数据结构的性能受到当今使用的同步方法(无论是基于锁还是无锁)的限制。
Fabien Gaud, Baptiste Lepers, Justin Funston, Mohammad Dashti, Alexandra Fedorova, Vivien Quéma, Renaud Lachaize, Mark Roth - 现代 NUMA 系统上内存管理的挑战
现代服务器级系统通常构建为放置在单个系统中的多个多核芯片。每个芯片都有一个本地 DRAM(动态随机存取存储器)模块;它们一起被称为一个节点。节点通过高速互连连接,并且系统完全连贯。这意味着,对于程序员来说是透明的,核心可以向其节点的本地内存以及其他节点的内存发出请求。关键的区别在于远程请求将花费更长的时间,因为它们会受到更长的线路延迟的影响,并且可能必须在遍历互连时跳转多个跃点。
Spencer Rathbun - 使用 Promise 的并行处理
在当今世界,有很多理由来编写并发软件。提高性能和增加吞吐量的需求导致了许多不同的异步技术。然而,所涉及的技术通常很复杂,并且是许多微妙错误的根源,特别是当它们需要共享可变状态时。如果不需要共享状态,那么这些问题可以通过一种更好的抽象来解决,称为 Promise。它们允许程序员将异步函数调用连接在一起,等待每个调用返回成功或失败,然后再运行链中下一个适当的函数。
Davidlohr Bueso - 实用同步原语的可扩展性技术
在理想的世界中,应用程序有望在越来越大的系统上执行时自动扩展。然而,在实践中,不仅这种扩展没有发生,而且常见的是,在这些更大的系统上,性能实际上会恶化。