荣格工业资源APP
了解工业圈,从荣格工业资源APP开始。
图片来源 / Unsplash
来源 / Semiengineering;荣格电子芯片翻译
作者 / Bryon Moyer
理论上,多核处理器可以并行运行多个代码线程,但目前某些类别的操作阻碍了通过并行计算来提高整体性能的尝试。
现在是该使用加速器来运行高度并行代码了吗?标准处理器具有许多 CPU,因此高速缓存一致性和同步可能涉及数千个低级系统代码周期,以保持所有内核的协调。CPU 根据 CPU 宽度和数据依赖性利用指令级并行性的能力也很有限。
这些 CPU 性能瓶颈是真实存在的、普遍存在的,并且不容易解决。尽管软件开发人员在创建可并行化算法方面发挥着巨大作用,但可能还有空间用于专门适合执行并行代码的硬件。
Part 1
三个主要瓶颈
CPU 架构师花费无数个周期来寻找改进处理器以提高性能或降低功耗的方法。这是不断推出改进型 CPU 的主要动力。“CPU 是为运行非结构化应用程序代码而构建的,”Quadric 首席营销官 Steve Roddy 解释道。“为了加快执行速度,并且不给程序员带来任何负担来考虑代码或目标机器,现代 CPU 已经积累了一份越来越奇特的功能清单,旨在尽可能快地运行随机的未知代码。”
多年来发展的技术包括:
超标量架构,其特点是解码器可以同时向一系列彼此并行的功能单元发出多个指令。
推测性执行,其中 CPU 在分支决策之前推测性地执行它认为最有可能的分支结果。如果猜测正确,那就领先一步。如果没有,它必须刷新管道并运行另一个分支。
无序执行,其中序列中的多个指令只要不相互依赖即可无序运行。
正在进行的内存访问跟踪,其中可以同时向内存发出多个内存请求。一些稍后发出的请求可能会提前返回,具体取决于拥塞和流量。
尽管做出了这些努力,但仍存在一些瓶颈。首先是从内存中获取数据所需的时间,这通常需要数百个周期。多个 CPU 和线程可以发出多个请求,重叠它们的访问时间,以最大限度地减少明显的延迟。缓存有助于最大限度地减少将来访问的延迟,但如果一个线程更改了其他线程正在使用的值,则一致性需要时间。
第二个瓶颈是线程之间的数据依赖关系引起的同步。如果多个线程想要访问相同的数据,则可能需要在更改数据时锁定该数据以供独占使用,然后在之后释放该锁。或者,如果多个线程对整体计算有贡献,则可能存在一个点,即所有线程必须先完成其工作,然后才能继续其他线程。管理同步的开销可能很大。
第三个瓶颈涉及指令级并行性,其中多个指令同时执行。超标量 CPU 明确启用该行为,但它们有限制。
“CPU 无法正确处理延迟隐藏。而且他们无法正确处理同步。而且他们无法正确处理低级并行性。”Flow Computing 的首席技术官兼首席架构师 Martti Forsell 说。
Part 2
一致性延迟
此外,缓存会针对较长的 DRAM 访问时间缓冲数据。但缓存系统通常是分层的,混合了私有缓存和共享缓存。“影响性能的基本选择是通过增加多级缓存来优化参考局部性,”Cadence 芯片解决方案组高级产品营销组总监 Arif Khan 说。1 级 (L1) 缓存最接近 CPU,并且优先考虑快速访问。它们通常是私有的,这意味着只有一个 CPU 可以访问它们。
一些 CPU 还提供私有 2 级 (L2) 缓存。这样做的好处是能够在缓存中保留更多数据,同时将其中一些数据降级到更大且速度稍慢(但更便宜)的 L2 缓存中。使用这些私有缓存,如果多个 CPU 需要访问相同的数据,则会在每个单独的缓存中保留一个副本。“在 CPU 中,由于私有缓存,一致性问题是不可避免的,”Forsell 指出。
一些 CPU 具有共享的 L2 缓存,并且大多数 3 级(或最后一级)缓存也是共享的。共享缓存的好处是多个 CPU 可以访问单个数据,而无需单独的副本。但 L2 共享通常是在两个或四个 CPU 之间进行的。因此,具有这种结构的 8 CPU 处理器将具有两个 L2 缓存,并且它们之间不会交叉共享。这意味着,如果在具有不同 L2 缓存的两个 CPU 上执行的两个线程需要相同的数据,则每个 L2 缓存都必须有自己的副本才能保持一致。只有最后一级高速缓存 (LLC) 始终在所有 CPU 之间共享,并且不需要一致性。
图 1:缓存层次结构示例。L1 缓存通常是一个 CPU 的专用缓存。L2 缓存也可能是,也可能在所有或部分 CPU 之间共享。最后一级缓存通常由所有人共享。来源:Bryon Moyer/Semiconductor Engineering
但即使是分享也会变得复杂。对于具有许多 CPU 的处理器,这些 CPU 通常分布在芯片上,因此很难放置一个统一的 LLC,以便所有 CPU 都能以相同的延迟访问它。更大的缓存是有代价的。“缓存越大,延迟就越高,”Rambus 的研究员兼杰出发明家 Steven Woo 指出。在实践中,虽然 LLC 在逻辑上被视为一个单元,但它在物理上被划分为多个块,每个块都靠近 CPU。
一致性协议已经建立多年。“MESI 是最常见的缓存一致性协议,它将缓存行描述为被修改 (M)、独占 (E)、共享 (S) 或无效 (I),”Arm 研究员兼 CPU 技术副总裁 Fred Piry 说。
“在这一点上,MESI 是一个相当稳定的协议,”Woo 观察到。“它并没有真正发生巨大变化的事实告诉我们,过去有很多工作 [对其进行微调]。”
CPU 向其他 CPU 发送消息,指示状态变化。“如果你正在写入已经共享的行,你需要告诉其他 CPU,'我正在修改那个缓存行,所以请忘记它。我现在有了一个新版本。在手机或笔记本电脑中,你没有那么多 CPU,所以它非常快,“Piry 说。“如果您在大型服务器中有数百个 CPU,则延迟可能是可见的。”
跟踪这些消息并非易事。“这可能很有挑战性,因为你可能会遇到这些消息通过系统相互追逐的情况,”Woo 说。“在最坏的情况下,如果你有一个大芯片,芯片一个角的内核必须使另一个角的内核失效。有时间遍历整个芯片,还有时间让消息在层次结构中发挥作用。
这通常发生在传输消息的专用 Coherency Mesh 上,对于较小的处理器,延迟可能限制为 10 个左右的周期。“支持 AMBA CHI 的 Arm 相干网状网络 (CMN) 就是这样一种结构,”Khan 指出。“IP 提供商在这个领域也有解决方案。”
但对于具有许多内核的处理器,该网格可能会在消息通过时遇到拥塞和延迟,最坏的情况可能花费 1000 个周期左右。
图 2:缓存更新延迟。对于多核系统,更新缓存可能需要大量的周期。来源:Bryon Moyer/Semiconductor Engineering
这种一致性活动发生在幕后的硬件和软件级别,因此应用程序软件开发人员几乎没有控制权。然而,它们最有可能根据程序的结构以及线程之间如何共享变量来影响性能。对于硬件设计人员来说,最好的选择是处理缓存大小、缓存隐私和一致性网格。改进它们可以提高性能,但会增加芯片尺寸和成本,并且功率更高。
“高效的连贯共享缓存架构对于最大限度地减少参与共享计算的所有线程的共享数据访问延迟是必不可少的,”Synopsys ARC-V RPX 处理器产品经理 Mohit Wani 说。
Part 3
同步延迟
两种主要的同步机制是锁(也称为互斥锁,即 “互斥”) 和 barrier。锁获取多个 CPU 可访问和正在使用的数据,并限制对单个 CPU 的访问。这有助于确保每个 CPU 都以相同的值处理相同的数据,并且没有 CPU 会使用旧版本或中间版本,而其他 CPU 则使用新版本。
屏障将代码的执行限制在特定点之外,允许所有对结果有贡献的线程在任何线程继续处理结果之前赶上并完成。减少算法是一个示例,它可能涉及多个线程计算单个结果。
至少有两种方法可以实现这两种工具,并且都有成本。“有时,当你到达障碍物时,人们所做的相当于睡着了,而像打断这样的事情会再次唤醒他们,”Woo 解释说。“中断成本高昂,而且发送所有发布消息和让内核重新启动并运行都需要花费大量时间。”好处是这样可以节省功耗,因为内核在等待时处于休眠状态。
另一种方法使用标志或信号量,内核执行一个繁忙的循环来轮询标志以查看它何时发生变化。“当内存位置的值发生变化时,他们就知道他们可以向前移动,”Woo 说。这种技术的响应速度要快得多,因为它不涉及中断,但在等待时旋转的内核会消耗更多的能量。
此外,在此期间,空闲核心上的线程可能会被其他线程交换,并且这些上下文交换的成本也很高。“当一个任务被暂停以产生处理器内核,同时等待其他任务赶上时,上下文切换开销会带来重大损失,”Wani 指出。
最后,就像一致性一样,一旦释放了 barrier 或 lock,消息就会传出到其他内核,对于多核单元,这些周期加起来可能达到数千个。“如果系统中有 100 个 CPU,命令将传播到系统中的所有 CPU,”Piry 解释说。“所有 CPU 都将收到同步请求,并确认它已收到并完成。此外,他们需要根据命令进行操作,而这可能需要数千个周期。
图 3:同步延迟。顶部显示 CPU n 设置屏障;然后,它比其他 CPU 需要更长的时间来计算其部分。因此,其他 CPU 必须在完成后等待,直到 CPU n 释放 barrier。同样,当 CPU 设置锁时,在释放锁之前,其他 CPU 都无法访问该数据。来源:Bryon Moyer/Semiconductor Engineering
“CPU 硬件可以通过提供指令集和内存架构来优化线程同步和数据共享,从而提供帮助,”Wani 说。一些用于嵌入式的小型处理器附带硬件信号量或邮箱,以加快同步速度。这种方法虽然适用于小型 CPU,但扩展性不佳。
更复杂的单元实现特定指令,例如内存中的原子。它们以原子方式执行读 / 修改 / 写序列,这意味着在结果完成之前,没有其他实体可以看到或影响结果。此类说明有助于避免需要显式同步的数据风险。
程序体系结构也是一个重要的工具。“最好通过采用一种软件架构来解决同步开销,这种架构可以降低线程需要同步的频率,”Wani 说。
Part 4
指令级并行执行
可以在多个级别进行。指令级并行是指同时执行多个指令。这是现代超标量 CPU 允许的,但有限制。“您可以在超标量流程中的多个功能单元中执行多个指令,但要求是指令必须是独立的,”Forsell 说。
图 4 显示了 10 宽 CPU 微架构的一部分,反映了当今市售的最宽 CPU。根据定义,此处可能的最大并行度是 10,但有两个 catch。第一个问题与功能单元的分布有关,而第二个问题则来自数据依赖关系。
一个 10 宽的 CPU 有 10 组可用的功能单元,哪组获取指令取决于指令是什么。在图 4 的虚构示例中,每组有两个功能单元,其中一个是整数 ALU。因此,对于整数运算,最多可以有 10 个函数一起运行。但只有 5 个具有浮点单元,2 个具有位操作单元,并且每个 1 个用于乘法器、除法器和融合乘法加法 (FMA)。因此,例如,最多可以同时运行两个 bit 运算,但乘法、除法和 FMA 不能并行。
无花果。4:超标量 CPU 函数单元。并非所有功能单元通道都能够实现相同的功能,有些功能可能仅在少数甚至一个通道中可用。来源:Bryon Moyer/Semiconductor Engineering
这些并行机会反映了最大可能的情况,并且仅当并行计算中涉及的变量彼此不依赖时,才会发生。例如,如果一个操作依赖于前一个操作的结果,那么这两个操作必须按顺序执行。
在这里,软件架构是可用于最大化并行度的最强大杠杆。“该算法始终是在给定硬件平台上进行优化以获得更好的性能的地方,”Khan 说。“用于模型复制和数据分区的分布式数据并行和完全分片数据并行技术可实现最佳系统利用率和模型性能。”
识别数据依赖关系需要软件分析。声明的变量可以静态分析,编译器可以以最大化目标 CPU 中性能的方式对目标代码进行排序。但是,许多程序广泛使用指针,并且无法静态分析这些指针,因为它们的值是实时确定的。可能需要进行动态分析,监控负载和存储的访问模式。在这种情况下,分析结果可以帮助开发人员优化程序的并行性,同时确保遵守依赖关系。
Part 5
控制和数据平面
对于通过一系列指令运行的线程,可以实现最大并行性,而没有任何分支(所谓的基本块)。分支会带来不确定性,随着系统的调整,推测性执行的尝试最终可能会以错误的猜测和打嗝告终。
工程师有时会描述两种编码样式:一种用于控制,一种用于数据操作。这种区别为网络处理芯片的架构奠定了基础,专用硬件用于对数据进行长串计算,并辅以 CPU 来执行控制代码,这通常涉及许多分支。数据平面中的代码比控制代码更适合并行执行。
鉴于在经典 CPU 架构中提高性能的机会有限,一些人认为,与单独使用 CPU 相比,为类似数据平面的代码提供单独的硬件单元将显著提高并行度。Roddy 说:“所有性能级别的平台现在都接受了从通用 CPU 中卸载结构化任务并将这些任务转移到特定于任务的专用处理引擎的想法。
Startup Flow Computing 提供了这样一个单元。“它是一个 IP 块,与 CPU 紧密集成到同一个芯片中,”Flow Computing 首席执行官 Timo Valtonen 解释道。“它会像 GPU 或神经加速器一样挂在总线上。它是一个可配置的小型处理器阵列,可以执行并行代码,而对屏障或锁的需求更少。
图 5:带有专用并行处理单元的拟议架构。CPU 控制流并将工作负载交给并行单元。CPU 有效地执行控制流,而并行单元执行相当于 data-place 代码的代码。来源:Bryon Moyer/Semiconductor Engineering
建议的单元具有一个由其所有内核共享的单个大型缓存,从而消除了一致性延迟。如果主 CPU 和并行单元都在处理一些相同的数据,那么 CPU 缓存和并行单元的缓存之间的一致性是必要的,但 Flow 认为这不太可能。
纤维的使用提供了一种轻量级的方法来处理原本是线的东西。它们由 CPU 创建,可以传递给加速器,并避免处理线程所需的操作系统服务调用。这可以缓解一些同步延迟。
然后,将指令链接在一起的能力可以最大限度地提高指令级并行性。“链接是获取一条指令的输出并将其直接馈送到下一条指令中的地方,”Woo 解释说。“自 1980 年代和 90 年代初以来,Cray 等公司的超级计算机一直在做这件事。”
使用并行单元需要为其重新编译代码。“我们确实应用了一种特殊的编译算法来处理具有链式执行依赖项的代码,”Forsell 说。新代码可以让 CPU 切换高度并行的部分,并且依赖关系分析允许链接将流经并行单元的指令。
但是,问题是整个代码库将使用 CPU 的本机语言。编译器不会为并行部分生成单独的目标代码。这意味着并行单元必须配备解码器和其他镜像随附 CPU 的 logic。此类工作需要与 CPU 供应商合作完成,特别是因为解码他人的指令集可能被视为侵犯知识产权。
然而,编译器并不是神奇的工具。“你必须记住,编译器更看重正确性而不是性能,”Woo 警告说。“他们有时不得不做出保守的假设,而不是激进的假设。为了充分利用系统,程序员可以输入指令或绕过编译器的自动代码生成。
Part 6
超越增量收益
尽管 CPU 设计人员无休止地工作以寻找提高性能的机会,但唾手可得的成果早已一去不复返。Roofline 模型描述了计算受内存带宽限制的位置以及计算受限的位置,可以帮助开发人员确定改进机会及其相关成本。但是,如果没有一些改变游戏规则的开发,每一代的改进都将是递增的。
Synopsys 指出了它用来提高性能的四种架构理念。
使更高级别的缓存可配置为更大的缓存或较小的缓存加上一些内存,并能够动态重新配置以适应当前工作负载;
在每个缓存级别,使用 pre-fetch 程序来隐藏更多的 fetch 延迟;
即使使用按顺序排列的 CPU,也允许无序内存请求,以便即使较早的请求仍处于待处理状态,也可以使用先返回的任何内容;
并且使用软件实现服务质量 (QoS),使某些内核优先于关键工作负载。
并联卸载装置会不会更改变游戏规则?这是可能的,前提是系统开发人员认为它正在顺利地滑入他们已经的工作方式。如果变化太多,天生保守的设计师会抵制这么多变化可能给项目带来的风险。此外,增加的硅空间必须增加足够的价值,以保持芯片的适当盈利能力。这可能使其对专用 SoC 更具吸引力,因为在这些 SoC 中,附加性能的价值是显而易见的。
与这种加速器相关的工具和开发流程也需要顺利地适应现有的流程,这里再次尊重编码人员对过度更改的抵触情绪。即使硬件想法及其相关工具在概念上无可指责,新用户仍然需要说服自己硬件和软件实现是正确且没有错误的。
Flow 是否取得成功可能有助于塑造此类技术的发展方向。如果失败,是因为根本的概念缺陷还是因为执行问题?如果是后者,其他人可能会捡起球并带着它跑动。如果是前者,那么整个想法就会受到质疑。
另一方面,如果它成功了,你可以打赌其他人会试图模仿和改进这种成功。鉴于这个提案的早期阶段,我们可能至少有一年甚至更长的时间来确定我们是否有获胜者。
原文链接:
https://semiengineering.com/cpu-performance-bottlenecks-limit-parallel-processing-speedups/
*声明:本文系原作者创作。文章内容系其个人观点,我方转载仅为分享与讨论,不代表我方赞成或认同,如有异议,请联系后台。