CPU架构
一个基本的CPU架构包含5个部分:输入、输出、存储、计算、控制。
摩尔定律指出集成电路每平方英寸的晶体管数量每18个月翻一番。
并行性
并行性是提高性能的关键,在并行性中,主要分为三个部分。
指令级并行(ILP):对于用户和程序员来说是隐式透明的,无需手动操作来实现指令的并行处理。常见的实现方式有:指令流水线、超标量执行、乱序执行、寄存器重命名、推测执行、分支预测等等。
任务级并行(TLP):对于用户和程序员是显式的,需要明确地进行任务划分和并行处理。常见的有:多线程/进程同时执行、多核处理器等等。
以及数据并行(Data Parallelism):特点是在数据上进行并行处理,对相同的操作在不同的数据上进行并行执行。常见的有向量处理器和SIMD。
CPU
CPU几乎可以说是我们当前人类工艺的集大成者,有诸多技术应用到其中。例如:
-
增加晶体管密度
随着半导体制造工艺的不断进步,CPU 中能够集成的晶体管数量越来越多。晶体管密度的增加意味着可以在相同的芯片面积上容纳更多的功能单元、缓存等,从而提高 CPU 的处理能力和存储能力,为实现更复杂的指令集、更强的并行处理能力等提供硬件基础。 -
提升晶体管的性能
晶体管性能的提升主要体现在其开关速度更快、功耗更低等方面。更快的晶体管能够使 CPU 的工作频率(时钟频率)不断提高。工作频率的提高意味着 CPU 在单位时间内可以执行更多的指令,就像工厂里的机器运转速度加快了,产品(指令执行结果)的产出速度也会相应加快,直接提升了 CPU 的计算性能。 -
数据通路更宽
数据路径宽度决定了 CPU 在一个时钟周期内可以处理的数据量。例如,从 32 位数据路径升级到 64 位数据路径,CPU 每次处理的数据量翻倍。这使得 CPU 在处理大数据量的运算(如高精度科学计算、大规模数据处理等)时能够更高效地完成任务,减少了处理数据所需的时钟周期数,提高了整体性能。 -
流水线技术
流水线技术将指令的执行过程分解为多个阶段,如取指、译码、执行、写回等。不同的指令可以在流水线的不同阶段同时进行,就像工厂里的流水线生产一样。这样,CPU 在一个时钟周期内可以同时处理多条指令的不同阶段,大大提高了指令的吞吐率。例如,在一个 5 级流水线的 CPU 中,理想情况下,每个时钟周期都可以有一条指令完成执行,而如果没有流水线技术,每个时钟周期只能处理一条指令的一个阶段,完成一条指令需要 5 个时钟周期,流水线技术使性能得到了显著提升。 -
超标量执行
超标量处理器可以在每个时钟周期内同时执行多条指令。它通过设置多个执行单元(如算术逻辑单元 ALU、浮点运算单元 FPU 等),并采用复杂的控制逻辑来调度指令,使得多条指令可以并行执行。例如,一个双发射的超标量处理器在每个时钟周期内可以同时发射两条指令到不同的执行单元进行处理,这进一步提高了指令的执行效率和 CPU 的性能。 -
投机执行
投机执行是一种优化技术,它允许 CPU 在遇到分支指令(如条件判断语句)时,不对分支方向进行等待,而是根据某种预测机制(如基于历史分支记录的预测)提前执行可能的分支路径上的指令。如果预测正确,这些提前执行的结果可以被利用,节省了等待分支判断的时间;如果预测错误,CPU 可以丢弃错误的执行结果并重新执行正确的分支路径。这种技术可以有效减少因分支指令导致的 CPU 空闲时间,提高 CPU 的利用率和性能。 -
缓存技术
缓存是一种位于 CPU 和主内存之间的高速存储器。由于主内存的访问速度相对较慢,CPU 通过缓存来暂存最近访问过的数据和指令。当 CPU 需要访问数据或指令时,首先会在缓存中查找,如果找到(命中),就可以快速地从缓存中读取,避免了访问主内存的长时间等待。缓存的使用大大提高了数据和指令的访问速度,从而提高了 CPU 的执行效率。通常,CPU 会有多个级别的缓存(如 L1、L2、L3 缓存),各级缓存的容量和访问速度有所不同,以在速度和成本之间进行平衡。 -
芯片和系统的集成
在芯片级别,将更多的功能模块(如 CPU 核心、图形处理单元 GPU、内存控制器等)集成到同一块芯片上,可以减少芯片之间的通信延迟,提高系统的整体性能。在系统级别,通过优化 CPU 与其他硬件组件(如主板、内存、存储设备等)的协同工作,如采用高速的总线接口、优化的电源管理系统等,可以充分发挥 CPU 的性能,同时提高整个计算机系统的可靠性和兼容性。这种集成方式也有助于减小系统的体积、降低功耗等。
现代计算机主要采用冯诺依曼架构体系,其中有三个核心组成部分:处理器、存储器、数据通路。而随着AI的发展,CPU和内存之间通信产生的瓶颈也越来越明显。
而其中带宽则是现代计算机系统之中关键中的关键,对于处理大量数据的大型并行系统来说尤其如此,虽然像缓冲、重排序、缓存等技术可以在一定程度上缓解带宽不足的问题,但他们终究只是权宜之计。缓冲可以暂时存储数据,等待带宽空闲时再传输;重排序可以优化数据传输顺序,提高带宽利用率;缓存可以减少对远程数据的访问频率,降低带宽需求。然而这些方法不能从根本上解决带宽不足对系统性能的限制。
ILP
指令流水线将指令的执行过程分解成为多个阶段,每个阶段由不同的处理器单元来完成,使得在同一时钟周期内,不同的指令可以处于不同的执行阶段,从而实现并行处理。这种技术使得处理器的每个部分都能保持忙碌,提高了处理器的利用率和指令的吞吐率。它允许在相同的时钟频率下实现更快的CPU吞吐量,在单位时间内可以处理更多的指令,但是同样可能会增加延迟,因为流水线本身就会带来额外的开销。下图展示了一个基本的五段流水线,包括取指、译码、执行、访存、写回五个阶段。
在了解流水线之后,我们可以进一步看一下超标量执行的概念。
超标量处理器能够在每个时钟周期内执行多条指令,通过将多条指令分派到处理器中的不同执行单元来实现这一点,每个执行单元并非单独的处理器,而是单个CPU内部的执行资源,例如算数逻辑单元ALU。
从图中我们可以看到,同时有两个模块在一起执行指令,一般来说超标量会和指令流水线结合使用,流水线爱你技术将指令的执行过程分解成为多个阶段,而超标量处理器则在流水线的每个阶段中并行执行多条指令,这种结合使得处理器能够在每个时钟周期内启动和完成多条指令,进一步提升了处理器的性能。
超标量还经常和乱序一起用,乱序调度逻辑需要占用CPU芯片相当大的区域。它需要维护指令间的依赖关系和指令队列,来应对硬件中动态调度的需求,这种乱序逻辑使得CPU能够在执行过程中动态调整指令执行顺序,提高指令并行性,减少停顿,但是同时也增加了硬件设计的复杂性和成本。
投机执行是乱序执行中的一个关键概念,它通过预测分支的指令结果来提前执行可能的指令路径,为了扩大可以并行执行的乱序指令窗口,CPU需要进行投机执行,但是这种方式可能会导致不必要的工作,如果预测错误,CPU需要放弃已经执行的错误部分,会浪费一定的计算资源,影响执行效率。
图中我们可以看到,在实际执行的过程中,需要构建一种依赖关系。
从图中我们可以看到add a, b, c和mul d, b, e没有依赖关系,可以并行执行,mul f, a, e依赖add a, b, c的结果,因为mul使用到了add中的a值,同理add a, d, g依赖于mul d, b, e的结果,因此我们可以看到,可以并行执行add和mul,再并行执行mul和add,最终执行fmul。
这种执行顺序和原来的执行顺序不同,因此它被称作乱序执行(Out-of-Order Execution,OoO)
了解完超标量之后,我们可以继续了解Verg Long Instruction Word(VLIW)。
VLIW允许处理器显式地指定一组指令在相同时间并行执行,这些指令由编译器调度,并将固定数量的操作格式化为一个大的指令(指令束)。它的优势在于能够减少多指令派发硬件需求,简化指令派发,不需要结构冒险检查逻辑,但是编译器工作量更大,需要负责确定哪些指令可以并行执行,处理器只需要按照指令束的指示执行即可。
VLIW高度依赖编译器,但是确实可以提高指令的并行性。
我们可以清晰地看到不同并行方式之间的差异。
ILP是计算机体系结构中的关键并行技术,而显式并行指令计算(Explicitly Parallel Instruction Computing,EPIC)是ILP的一种实现方式。
EPIC是从VLIW架构演变而来,VLIW架构将多条指令组合成一个长指令字,由编译器负责指令调度和依赖分析,硬件则按顺序执行指令束中的指令,EPIC在此基础上进行了改进和发展。
EPIC将指令调度的复杂度进行转移。在传统架构中,指令调度的复杂性主要由CPU硬件来承担,这使得硬件设计变得复杂并且成本高昂。而EPIC架构将这种复杂性转移到了软件编译器上。编译器在程序执行前就对指令进行分析和调度,确定哪些指令可以并行执行,并将这些信息明确地编码在指令中,从而简化了硬件的设计,降低硬件成本。
在EPIC架构中,每条指令可以编码多个操作,这些操作会被分配给多个单元同时处理,这使得处理器能够在一个周期内执行多个操作,提高指令执行效率。
超越VLIW的特性:
- 指令束:将一组多条软件指令组合成一个束(Bundle),每个指令束可以包含多条指令,这些指令在硬件执行时会被同时处理,提高指令的并行性。
- 停止位:每个指令束都有一个停止位,用于指示当前指令束的操作是否会被后续指令束所依赖。如果停止位被设置,表示后续指令束需要等待当前指令束完成后再执行,否则可以继续执行后续指令束,这有助于更好地管理指令之间的依赖关系,避免数据冲突和错误执行。
- 软件预取指令:EPIC架构引入了软件预取指令,这是一种数据预取技术。编译器可以根据程序的访问模式和数据依赖关系,在需要数据之前将其从内存中预取到缓存中,从而减少数据访问延迟,提高程序的执行速度。
- 推测性加载指令:允许处理器在不确定数据是否会被实际使用的情况下,提前将数据从内存中加载到寄存器中,如果后续发现该数据确实需要被使用,则可以节省加载数据的时间;如果发现数据不需要被使用,则可以丢弃加载的数据,重新执行正确的操作。
- 验证加载指令:与推测性加载指令配合使用,用于检查推测性加载的数据是否依赖于后续的存储操作,如果发现存在依赖关系,则需要重新加载数据,以确保数据的正确性。
并行处理指令
并行处理指令有三大任务:
1. 检查指令间的依赖关系
要确定哪些指令可以并行执行,必须先弄明白指令之间是否存在数据依赖、控制依赖等关系。比如,后一条指令是否用到了前一条指令的计算结果,如果有这种依赖,那么就不能简单地并行执行。
2. 分配指令到硬件的功能单元
处理器有多种功能单元,像整数运算单元、浮点运算单元等。得合理地把指令分配到合适的功能单元去执行。
3. 确定指令何时启动并组成一个指令束
需要决定把哪些指令打包成一个指令束,以及何时开始执行这个指令束,这涉及到对指令执行时机的把控。
这个表格展示对比了超标量、EPIC、Dynamic VLIW、VLIW之间的差异。 我们可以看到Superscalar完全由硬件完成,对硬件设计要求高,但是能灵活应对各种复杂的指令序列。
EPIC由编译器负责。编译器在程序执行前就分析指令间的依赖关系,把可以并行执行的指令提前分好组,减轻了硬件在这方面的负担。
VLIW这三大任务都由编译器来完成。编译器需要全面分析指令的依赖关系、分组、分配功能单元以及确定启动时机,并把这些信息编码在指令束中。硬件按编译好的指令束顺序执行,硬件设计相对简单,但对编译器要求极高,一旦编译器分析不准确,可能会影响指令并行执行的效果。
最终我们来看一下不同架构的对比: