本章简要概述了对性能有影响的关键CPU架构和微架构特征。本章的目标不是详细介绍CPU架构的细节和权衡,在文献中已有广泛介绍[Hennessy和Patterson,2011]。我们将快速回顾对软件性能产生直接影响的CPU硬件特征。
3.1 Instruction Set Architecture
指令集是软件与硬件通信时使用的词汇。指令集架构(ISA)定义了软件和硬件之间的约定。Intel x86、ARM v8、RISC-V是当前最广泛部署的ISA的示例。所有这些都是64位体系结构,即所有地址计算都使用64位。ISA开发人员通常确保符合规范的软件或固件将在使用规范构建的任何处理器上执行。广泛部署的ISA特许经营权通常也会确保向后兼容性,从而使为处理器的GenX版本编写的代码将继续在GenX+i上执行。
大多数现代体系结构都可以归类为通用寄存器架构的装载-存储体系结构,操作数是显式指定的,并且只使用装载和存储指令访问内存。除了提供ISA中的基本功能,例如使用整数和浮点数执行标量算术操作、加载、存储、控制等之外,广泛部署的体系结构继续增强其ISA以支持新的计算范例。这些包括增强的向量处理指令(例如Intel AVX2、AVX512、ARM SVE)和矩阵/张量指令(Intel AMX)。软件映射到使用这些高级指令通常可以提供数量级的性能改进。
随着深度学习领域的快速发展,行业对于替代数值格式以实现显著的性能提升再次产生了兴趣。研究表明,使用更少的位数来表示变量深度学习模型的性能表现同样好,可以节省计算和内存带宽。因此,除了传统的32位和64位格式之外,几个CPU特许经营权最近还添加了对低精度数据类型的支持,例如8位整数(int8,例如Intel VNNI)、16位浮点数(fp16、bf16)在ISA中。
3.2 Pipelining //流水线
流水线是使CPU快速的基础技术,其中多个指令在执行过程中重叠。 CPU中的流水线从汽车装配线中获得启发。指令的处理被划分为不同的阶段。这些阶段并行运作,处理不同指令的不同部分。 DLX是[Hennessy and Patterson,2011]定义的简单5级流水线的示例,包括:
1. 指令获取(IF)
2. 指令解码(ID)
3. 执行(EXE)
4. 存储器访问(MEM)
5. 写回(WB)
Figure 7: Simple 5-stage pipeline diagram.
图7显示了5级流水线CPU的理想流水线视图。在第1个时钟周期中,指令x进入流水线的IF阶段。在下一个时钟周期中,当指令x移动到ID阶段时,程序中的下一条指令进入IF阶段,以此类推。一旦流水线被填满,例如在上面的第5个时钟周期中,CPU的所有流水线阶段都正在处理不同的指令。如果没有流水线技术,指令x + 1将无法开始执行,直到指令1完成其工作为止。
CPU流水线技术是需要多核并行是么?
CPU流水线技术不需要多核并行。相反,流水线技术是通过将处理器的指令执行过程分为多个阶段,将各个阶段的操作重叠,在单个核心上实现指令级并行,从而提高CPU的性能。流水线技术利用了指令之间的独立性,通过同时执行多条指令来提高CPU吞吐量。因此,即使在单核CPU上,流水线技术也可以显著提高指令执行的速度和效率。
//可能是单核CPU处理IF/ID/EXE/MEM/WB的模块只有一个,同一时刻只能处理一个IF/ID/EXE/MEM/WB
大多数现代CPU都是采用深度流水线技术,也称为超级流水线技术。流水线CPU的吞吐量定义为每单位时间内完成并退出流水线的指令数量。任何给定指令的延迟是流水线中所有阶段的总时间。由于流水线中的所有阶段都互相链接,因此每个阶段必须准备好步调一致地移动到指令处。将指令从一个阶段移动到另一个阶段所需的时间定义了CPU的基本机器周期或时钟。对于一个给定的流水线,所选择的时钟值由最慢的流水线阶段定义。CPU硬件设计师努力平衡可以在一个阶段中完成的工作量,这直接定义了CPU的操作频率。增加频率会提高性能,并通常涉及平衡和重新流水线化,以消除由最慢的流水线阶段引起的瓶颈。
在理想的、完全平衡且不会产生任何停顿的流水线中,流水线机器中每条指令的时间由以下公式给出:
流水线机器中每条指令的时间 = 非流水线机器中每条指令的时间 / 流水线阶段数
但在实际实现中,流水线引入了几个限制条件,限制了上述理想模型。流水线故障会阻止理想的流水线行为导致停顿。故障分三类:结构故障、数据故障和控制故障。幸运的是,对于程序员来说,在现代CPU中,所有类型的故障都由硬件处理。
//所谓的非流水线机器中每条指令的时间指的是instruction完全串行指令的时间
结构性故障是由于资源冲突所造成的。在很大程度上,它们可以通过复制硬件资源来消除,例如使用多端口寄存器或内存。然而,消除所有这些故障可能会在硅芯片面积和功耗方面变得相当昂贵。因此,在设计中需要权衡不同的需求和限制,以最小化产生结构性故障的可能性,并且尽可能减少额外的硬件成本。现代CPU采用了各种技术来减少结构故障的影响,如乱序执行、数据预取和缓存等技术。这些技术可以提高CPU的性能,降低延迟,同时还能保持设计的经济性和可行性。
数据故障是由程序中的数据依赖关系引起的,并分为三种类型:
1. Read-after-write(RAW)故障需要依赖读取在写入之后才执行。当指令x+1在先前的指令x写入源之前读取源时,就会发生这种情况,导致读取错误的值。CPU实现了从管道后期到管道前期的数据转发(称为“bypassing”)来减轻RAW故障所带来的惩罚。其基本思想是,可以在指令x完全执行之前将指令x的结果转发给指令x+1。在上面的例子中,对于寄存器R1存在RAW依赖性。可以直接在执行R0 ADD 1操作之后(从EXE管道阶段),获取该值并写入寄存器文件,无需等待WB阶段完成。Bypassing有助于节省几个时钟周期。管道越长,bypassing的效果也就越明显。
2. Write-after-read(WAR)故障需要依赖写操作在读操作之后执行。当指令x+1在先前的指令x读取源之前写入源时,就会发生WAR故障,导致读取错误的新值。WAR故障不是真正的依赖关系,可以通过一种称为寄存器重命名的技术来消除。这种技术可以将逻辑寄存器从物理寄存器中抽象出来。CPU通过保留大量的物理寄存器支持寄存器重命名。逻辑寄存器是由ISA定义的,只是更宽的寄存器文件中的别名。通过这种对体系结构状态的解耦,可以轻松地解决WAR故障。例如:
R1 = R0 ADD 1
R0 = R2 ADD 2
对于寄存器R0存在WAR依赖性。由于有大量物理寄存器可用,因此我们可以从写操作开始并向下重命名所有R0寄存器的所有出现。一旦重命名了R0寄存器以消除WAR故障,就可以安全地以任何顺序执行这两个操作。
3. Write-after-write(WAW)故障需要依赖的写操作在另一个写操作之后才执行。当指令x+1在指令x写入源之前写入源时,会导致写入顺序错误。WAW故障也可以通过寄存器重命名来消除,允许两个写操作以任何顺序执行,同时保留正确的最终结果。
控制故障是由程序流程的改变引起的。这些故障来自于加入了流水线技术的分支和其他改变程序流程的指令。确定分支方向(成功或失败)的分支条件在执行管道阶段中解析。因此,除非消除控制故障,否则下一条指令的获取将无法进行流水线处理。使用动态分支预测和推测执行等技术可以克服控制故障。详细内容请参考下一节。
3.3 Exploiting Instruction Level Parallelism (ILP) //利用指令级并行性
程序中的大多数指令都适合进行流水线处理并并行执行,因为它们是独立的。现代CPU实现了大量的额外硬件特性来利用这种指令级并行性(ILP)。这些硬件特性与先进的编译器技术协同工作,可以显著提高性能。
3.3.1 OOO Execution //乱序执行(Out-of-Order Execution,OOO Execution)
图7中的流水线示例展示了所有指令按照程序中的顺序有序地通过不同的流水线阶段。然而,大多数现代CPU支持乱序执行(OOO Execution)——即顺序指令可以以任意顺序进入执行流水线阶段,只受它们之间的依赖关系限制。OOO Execution的CPU仍然必须给出与按程序顺序执行所有指令相同的结果。当指令被最终执行且其结果在体系结构状态中是正确的并可见时,称其为“已重命名”。为确保正确性,CPU必须按照程序顺序重命名并发射所有指令。乱序执行主要用于避免由依赖关系引起的停滞所导致的CPU资源利用不足,特别是在下一节中描述的超标量引擎中。
使用先进的硬件结构诸如记分牌和技术诸如寄存器重命名可以实现这些指令的动态调度,以减少数据冲突。记分牌被用来调度顺序的重命名以及所有机器状态更新。它跟踪每个指令的数据依赖关系以及数据在流水线中的可用位置。大多数实现均努力平衡硬件成本和潜在收益。通常,记分牌的大小决定了硬件可以向前看多远以调度这些独立指令。
Figure 8: The concept of Out-Of-Order execution.
图8详细说明了一个示例下乱序执行的基本概念。假设由于某种冲突,指令x+1不能在4和5周期中执行。在一个顺序执行的CPU中,所有后续指令都会被阻塞不允许进入执行(EXE)流水线阶段。但在乱序执行的CPU中,不具有任何冲突的后续指令(例如指令x+2)可以进入并完成执行。所有指令仍然以程序顺序重命名且执行,即这些指令按照程序的顺序完成写回(WB)阶段。
3.3.2 Superscalar Engines and VLIW //超标量引擎和VLIW
Superscalar Engines和VLIW(Very Long Instruction Word)是现代CPU设计中两种常见的指令级并行处理技术。在Superscalar Engines中,CPU通过同时发射多个指令来提高执行效率。这些指令可以相互独立,从而可以通过乱序执行和动态调度来充分利用处理器资源。VLIW则使用一个长指令字(Very Long Instruction Word)包含多个指令,并且这些指令共享一个执行单元和总线,从而实现高吞吐量的指令流水线。
这两种技术都依赖于处理器能够在单个时钟周期内执行多个指令,并且需要具有复杂的硬件结构来支持指令突发执行。因此,他们通常更适合用于执行大量并行任务的应用程序,例如多媒体处理或科学计算。
现代大多数CPU都是超标量的,即它们可以在同一周期内发射多于一个指令。发射宽度(Issue-width)是在同一周期内可以发射的最大指令数。当前一代CPU的典型发射宽度范围为2-6。为了确保正确的平衡,这种超标量引擎还支持多个执行单元和/或流水线执行单元。CPU还将超标量性能与深度流水线和乱序执行相结合,以提取给定软件的最大指令级并行性(Instruction-Level Parallelism)。
图9展示了一款支持2个宽度的超标量CPU的示例,即在每个流水线阶段处理两条指令。超标量CPU通常支持多个独立的执行单元,以避免冲突并保持流水线中的指令流动性。相比图7所示的简单流水线处理器,复制的执行单元可以增加机器的吞吐量。
Figure 9: The pipeline diagram for a simple 2-way superscalar CPU.
像英特尔的Itanium这样的架构使用了一种称为VLIW(Very Long Instruction Word)的技术,将超标量、多执行单元机器的调度负担从硬件转移到编译器。其基本原理是通过要求编译器选择正确的指令组合来保持机器的充分利用,从而简化硬件。编译器可以使用诸如软件流水线、循环展开等技术,向前查找更远的指令,找到正确的指令级并行性(ILP),超出硬件结构的合理支持范围。
这种架构对编译器的要求更高,需要编译器能够优化代码以充分利用硬件资源,因此编译器设计变得更加复杂。VLIW架构的优点是可以避免在CPU内部进行复杂的依赖分析和调度,从而使CPU硬件更加简单、高效。
3.3.3 Speculative Execution //预测执行
如前一节所述,如果指令因为控制相关风险而被阻塞,将会对流水线产生显著的性能损失。为了避免这种性能损失,一种技术是利用硬件分支预测逻辑来预测分支的可能方向,并允许从预测路径中执行指令(即预测执行)。
我们考虑一个简短的代码示例,如清单3所示。为了让处理器知道它应该执行哪个函数,它应该知道条件a < b是false还是true。不知道这一点,CPU就会等待分支指令的结果确定,如图10a所示。
Listing 3 Speculative execution
if (a < b)
foo();
else
bar();
使用预测执行,CPU对分支的结果进行猜测,并开始处理从所选择的路径中选择的指令。假设处理器预测条件a < b将被评估为true。它在不等待分支结果的情况下继续执行,并推测性地调用函数foo(见图10b,用*标记了推测性的工作)。为了确保机器的架构状态不会受到推测式执行指令的影响,机器状态的更改不能被提交。在上面的例子中,分支指令比较了两个标量值,这很快。但实际上,分支指令可能依赖于从内存中加载的值,这可能需要数百个周期。如果预测是正确的,它可以节省很多周期。然而,有时预测是错误的,应该调用函数bar。在这种情况下,预测执行的结果必须被取消并丢弃。这就是所谓的分支预测失误惩罚,我们将在第4.8节中讨论。
Figure 10: The concept of speculative execution.
为了跟踪预测执行的进度,CPU支持一种称为重新排序缓冲区(ROB)的结构。ROB维护所有指令执行的状态,并按顺序退役指令。来自预测执行的结果被写入ROB,并且只有在预测正确的情况下才按程序流程的相同顺序提交到架构寄存器中。CPU还可以将预测执行与乱序执行相结合,并使用ROB跟踪预测和乱序执行。
3.4 Exploiting Thread Level Parallelism //利用线程级并行性
之前描述的技术依赖程序中可用的并行性来加速执行。此外,CPU支持利用在CPU上执行的进程和/或线程之间的并行性的技术。硬件多线程CPU支持专用硬件资源,在CPU内独立地跟踪每个线程的状态(也称为上下文),而不是仅跟踪单个执行线程或进程的状态。采用这种多线程CPU的主要动机是在线程由于长延迟活动(如内存引用)而被阻塞时,以最小的延迟从一个上下文切换到另一个上下文(而无需承担保存和恢复线程上下文的成本)。
3.4.1 Simultaneous Multithreading //同时多线程(Simultaneous Multithreading,SMT)
现代CPU通过支持同时多线程来结合ILP技术和多线程,从可用的硬件资源中挖掘最高的效率。来自多个线程的指令在同一周期内同时执行。同时从多个线程调度指令可以增加利用可用超标量资源的概率,提高CPU的整体性能。为了支持SMT,CPU必须复制硬件以存储线程状态(程序计数器、寄存器)。跟踪乱序和预测执行的资源可以复制或分区到线程之间。通常,缓存资源在硬件线程之间动态共享。
3.5 Memory Hierarchy
为了有效地利用CPU中预留的所有硬件资源,机器需要在适当的时间提供正确的数据。理解内存层次结构对于发挥CPU的性能能力至关重要。大多数程序表现出局部性的特性;它们不会均匀地访问所有代码或数据。CPU内存层次结构建立在两个基本属性上:
• 时间局部性: 当给定的内存位置被访问时,很可能在不久的将来再次访问相同的位置。理想情况下,我们希望下次需要该信息时可以在缓存中找到它。
• 空间局部性: 当给定的内存位置被访问时,很可能在不久的将来访问附近的位置。这是指将相关数据放置在彼此附近。当程序从内存读取单个字节时,通常会获取更大的内存块(缓存行),因为程序很可能很快需要访问该数据。
本节提供了现代CPU支持的内存层次结构系统的关键属性概述。
3.5.1 Cache Hierarchy
缓存是CPU流水线发出的任何请求(用于代码或数据)的内存层次结构的第一级。理想情况下,流水线带有无限缓存和最小访问延迟会有最佳表现。实际上,任何缓存的访问时间都会随着其大小的增加而增加。因此,缓存被组织为由靠近执行单元的小型、快速存储块组成的层次结构,并由较大、较慢的块支持。缓存层次结构的特定级别可以专门用于代码(指令缓存,i-cache)或数据(数据缓存,d-cache),或共享代码和数据(统一缓存)。此外,层次结构的某些级别可以专用于特定CPU,而其他级别可以在多个CPU之间共享。
缓存被组织为块,具有定义好的块大小(缓存行)。现代CPU中典型的缓存行大小为64字节。最靠近执行流水线的缓存大小通常在8KiB到32KiB之间。在层次结构中更远的缓存可以是64KiB到16MiB。任何缓存级别的架构由以下四个属性定义。
3.5.1.1 Placement of data within the cache.
请求的地址用于访问缓存。在直接映射(direct-mapped)缓存中,给定的块地址只能出现在缓存中的一个位置,并由下面的映射函数定义。
缓存中的块数 = 缓存大小 / 缓存块大小
直接映射位置 = (块地址) mod (缓存中的块数)
在完全关联(fully associative)的缓存中,给定的块可以放置在缓存中任何位置。
直接映射和完全关联映射之间的中间选项是集合关联映射。在这样的缓存中,块被组织为集合,通常每个集合包含2、4或8个块。给定的地址首先映射到一个集合。在一个集合中,该地址可以放置在该集合中的任何块中。一个每个集合具有m个块的缓存被描述为m路集合关联缓存。集合关联缓存的公式为:
缓存中的集合数 = 缓存中的块数 / 每个集合中的块数(关联度)
集合(m路)关联位置 = (块地址) mod (缓存中的集合数)
3.5.1.2 Finding data in the cache.
m路集合关联缓存中的每个块都有一个与之相关联的地址标记(address tag)。此外,标记还包含状态位,如有效位(valid bit),用于指示数据是否有效。标记还可以包含其他位,用于指示访问信息、共享信息等,在后续章节中将进行描述。
Figure 11: Address organization for cache lookup.
图11展示了如何使用管线生成的地址来检查缓存。低阶地址位定义给定块内的偏移量;块偏移位(32字节缓存行为 5 位,64字节缓存行为 6 位)。使用上述公式基于索引位选择集合(set)。一旦选择了集合,标记位(tag)被用于与该集合中的所有标记比较。如果一个标记(tag)与输入请求的标记匹配并且有效位被置位,那么就会发生缓存命中。与该块条目相关联的数据(从缓存的数据数组中并行读出)将提供给执行管线。当标记不匹配时,会发生缓存未命中。
3.5.1.3 Managing misses. //处理缓存未命中
当发生缓存未命中时,控制器必须选择一个要替换的缓存块来分配产生未命中的地址。对于直接映射缓存,由于新地址只能在单个位置中分配,因此映射到该位置的先前条目被取消分配,并将新条目安装在其位置。对于集合关联缓存,由于新的缓存块可以放置在集合中的任何块中,因此需要使用替换算法。通常使用的替换算法是LRU(最近最少使用)策略,在此策略中,最近最少访问的块将被淘汰以为未命中地址腾出空间。另一种选择是随机选择一个块作为被替换的块。大多数CPU在硬件上定义了这些功能,使得执行软件更加容易。
3.5.1.4 Managing writes.
读取缓存是最常见的情况,因为程序通常读取指令,而数据的读取要比数据的写入更大。处理缓存中的写入更加困难,CPU实现使用各种技术来处理这种复杂性。软件开发人员应特别注意硬件支持的各种写缓存流程,以确保其代码具有最佳性能。
CPU设计使用两个基本机制来处理在缓存中命中的写入:
• 在写穿透缓存中,命中的数据被写入缓存块和层次结构下一级。
• 在写回缓存中,命中的数据只写入缓存。随后,较低层次结构包含陈旧的数据。修改行的状态通过标记中的脏位进行跟踪。当修改的缓存行最终从缓存中驱逐时,写回操作强制将数据写回到下一级。
解释下CPU的写穿透缓存和写回缓存:
CPU的写穿透缓存和写回缓存是两种不同的处理器缓存写入的机制。
1. 写穿透缓存:当CPU执行写操作时,如果该数据在缓存中已经存在,则该数据被同时写入到缓存和下一级存储器(如内存)中。因此,写穿透缓存将确保每次写操作都会立即更新缓存,并且会将数据保存到下一级存储器中,以保持缓存和下一级存储器之间的同步。这种方法可以保证缓存中的数据与下一级存储器中的数据一致性,但是在写入速度上会比较慢,因为每次写操作都需要写入到缓存和下一层存储器中。
2. 写回缓存:当CPU执行写操作时,如果该数据在缓存中已经存在,则该数据仅被写入到缓存中,而不会立即更新下一级存储器(如内存)。在写回缓存中,修改行的状态通过标记中的脏位进行跟踪。只有当缓存行最终从缓存中驱逐出去时,才会进行写回操作,将修改的数据写回到下一级存储器中。这种方法可以提高写操作的速度,因为它省去了每次写操作都需要写入到下一级存储器的步骤,但是会导致缓存中的数据和下一级存储器中的数据不同步,因此需要进行额外的机制来处理这种不同步问题。
写操作时缓存未命中的情况可以使用两种不同的选项来处理:
• 写分配或写未命中时获取缓存,未命中位置的数据从层次结构的下一级加载到缓存中,并且随后的写操作会像写命中一样处理。
• 如果缓存使用无写分配策略,则缓存未命中事务将直接发送到层次结构的较低级别,且块不会加载到缓存中。
在这些选项中,大多数设计通常选择采用写回缓存和写分配策略,因为这两种技术都试图将后续的写事务转换为缓存命中,而无需向层次结构的较低级别添加额外的流量。写穿透缓存通常使用无写分配策略。
//写回缓存--写分配策略,写穿透缓存--无写分配策略
3.5.1.5 Other cache optimization techniques.
对于程序员来说,了解缓存层次结构的行为对于从任何应用程序中提取性能至关重要。当CPU时钟频率增加而内存技术速度落后时,这一点尤其明显。从流水线的角度来看,访问任何请求的延迟由以下公式给出,可以递归地应用于缓存层次结构的所有级别,直到主存储器:
平均访问延迟=命中时间+失效率×失效代价
硬件设计师通过许多新颖的微架构技术来减少命中时间和失效代价,以应对这一挑战。从根本上讲,缓存未命中会使流水线停滞并损害性能。任何缓存的失效率高度依赖于缓存架构(块大小、关联度)和运行在机器上的软件。因此,优化失效率成为硬件-软件协同设计的努力。如前面所述,CPU为缓存提供了最佳的硬件组织。下面描述了可以在硬件和软件中实现以最小化缓存失效率的其他技术。
3.5.1.5.1 HW and SW Prefetching. //硬件和软件预取
减少缓存失效和随后的停顿的一种方法是,在流水线要求之前将指令和数据预取到不同级别的缓存层次结构中。假设预取请求可以足够提前发出,那么处理失效代价的时间就可以被隐藏起来。大多数CPU都支持基于硬件的预取器,程序员可以控制它们。
硬件预取器观察运行应用程序的行为,并在缓存失效的重复模式上启动预取。硬件预取可以自动适应应用程序的动态行为,例如不同的数据集,并且不需要优化编译器或分析支持。此外,硬件预取不需要额外的地址生成和预取指令的开销。然而,硬件预取仅限于学习和预取实现在硬件中的有限的一组缓存失效模式。软件内存预取是对硬件预取的补充。开发人员可以通过专用的硬件指令(参见8.1.2节)提前指定哪些内存位置是需要的。编译器也可以自动将预取指令添加到代码中,以在所需之前请求数据。预取技术需要在需求和预取请求之间取得平衡,以防止预取流量减慢需求流量。
3.5.2 Main Memory
主存是从缓存向下的层次结构中的下一个级别。主存使用支持大容量和合理成本的动态RAM(DRAM)技术。主存由三个主要属性描述,即延迟、带宽和容量。延迟通常由两个组件来指定。内存访问时间是从请求到数据字可用之间经过的时间。内存周期时间定义了连续两个访问内存之间所需的最小时间。
DDR(双倍数据率)DRAM技术是大多数CPU支持的主要DRAM技术。历史上,DRAM带宽在每一代中都有所提高,而DRAM延迟则保持不变甚至有所增加。表2显示了最近三代DDR技术的最高数据传输速率和相应的延迟。数据速率以每秒百万次传输(MT/s)为单位进行测量。
表2: 最近三代DDR技术的最高数据传输速率和相应的延迟。
新型DRAM技术,如GDDR(图形DDR)和HBM(高带宽内存),被使用于需要更高带宽,而不受DDR接口支持的定制处理器中。现代CPU支持多个独立的DDR DRAM内存通道。通常,每个内存通道的宽度为32位或64位。
3.6 Virtual Memory
虚拟内存是一种机制,用于将连接到CPU的物理内存与在CPU上执行的所有进程共享。虚拟内存提供了一种保护机制,限制其他进程从分配给特定进程的内存中访问该内存。虚拟内存还提供了重定位功能,即能够在物理内存的任何位置加载程序,而无需更改程序的寻址方式。
在支持虚拟内存的CPU中,程序使用虚拟地址进行访问。这些虚拟地址通过专用的硬件表进行翻译,这些表提供了虚拟地址和物理地址之间的映射关系,也被称为页表。地址转换机制如下所示。虚拟地址分为两部分。虚拟页号用于索引到页表(页表可以是单层或嵌套的)以生成虚拟页号和相应物理页之间的映射。然后使用来自虚拟地址的页偏移量来访问映射到的物理页中相同偏移量的物理内存位置。如果请求的页面不在主存中,则会导致页面错误。操作系统负责向硬件提供提示以处理页面错误,使得最近最少使用的一页可以被替换以为新页面腾出空间。
Figure 12: Address organization for cache lookup.
CPU通常使用分层页表格式,以高效地将虚拟地址位映射到可用的物理内存。在这样的系统中,页面缺失是昂贵的,需要遍历整个分层结构以获取对应的物理地址。为了降低地址转换时间,CPU支持一种称为“转换后备缓冲器”(Translation Lookaside Buffer,TLB)的硬件结构,以缓存最近使用的地址转换。
3.7 SIMD Multiprocessors
什么是SIMD:
SIMD是“单指令流多数据流”(Single Instruction Multiple Data)的简称,它是一种并行计算的技术,旨在同时对多个数据执行相同的操作。这种技术可以通过在一次执行中处理多条数据来提高处理器性能,特别是在涉及大量数据的情况下。常用于图形处理、信号处理、科学计算以及人工智能等领域。
Figure 13: Example of scalar and SIMD operations.
//scalar标量
Listing 4 SIMD execution
double *a, *b, *c;
for (int i = 0; i < N; ++i) {
c[i] = a[i] + b[i];
}
图13展示了List 4中代码在标量模式和SIMD执行模式下的情况。在传统的SISD(单指令,单数据)模式下,加法操作会分别应用于数组a和b的每个元素。然而,在SIMD模式下,添加操作可以同时应用于多个元素。SIMD CPU支持执行单元,能够对矢量元素执行不同的操作。数据元素本身可以是整数或浮点数。SIMD架构允许更高效地处理大量的数据,并且最适合涉及矢量操作的数据并行应用程序。
大多数流行的CPU架构都具有矢量指令,包括x86、PowerPC、ARM和RISC-V。在1996年,英特尔发布了一个新的指令集——MMX,它是一种针对多媒体应用程序设计的SIMD指令集。随着MMX的推出,英特尔推出了新增能力和增加矢量大小的新指令集:SSE、AVX、AVX2和AVX512。新的SIMD指令集一经问世,就开始了让软件工程师使用它们的工作。起初,这些新的SIMD指令是通过汇编语言编写的。后来,特殊的编译器内嵌函数被引入。今天,所有主要的编译器都支持针对流行处理器的矢量化。
3.8 Modern CPU design
图14中的块图显示了英特尔第六代核心Skylake的详细信息,该核心于2015年发布并广泛传播于全世界。Skylake核心被分成一个按顺序的前端和一个8路超标量的乱序后端,前端会将x86指令提取和解码为u-op(微操作)。该核心支持2路SMT。它具有32KB、8路第一级指令缓存(L1 I-cache)和32KB、8路第一级数据缓存(L1 D-cache)。L1缓存由一个统一的1MB二级缓存L2缓存支持。L1和L2缓存是各自核心私有的。
Figure 14: Block diagram of a CPU Core in the Intel Skylake Microarchitecture.
什么是superscalar超标量:
Superscalar是指CPU具有同时执行多个指令的能力。具体来说,它可以在一个时钟周期内发射多个指令并对它们进行指令级并行(ILP)执行。超标量是一种广义上的超标量架构,它的主要特点是使用了多个指令调度单元(ISU),允许多条指令同时执行。与传统的单流水线处理器相比,超标量CPU可在给定的时间内执行更多的指令。
什么是SMT:
SMT全称为超线程技术(Simultaneous Multi-Threading),是一种多线程技术。SMT可以在单一CPU核心上同时支持两个线程,在同一个时钟周期中交替执行这两个线程,从而提高处理器的利用率和吞吐量。对于具有SMT技术的处理器来说,操作系统会将其视为拥有多个逻辑处理器,可以为每个逻辑处理器分配不同的任务,从而实现更高效的并行计算。
SMT和传统理解的软件多线程的区别是?
SMT和传统软件多线程的区别在于他们的实现层面。传统的软件多线程是使用软件来管理多个线程的执行,这些线程共享同一个物理CPU核心,每个线程在时间片轮流占用CPU时间。而SMT则是在硬件层面上实现多线程的,物理CPU核心会被复制成多个逻辑处理器,并为每个逻辑处理器分配不同的任务,从而提高整个系统的并行运行效率。在操作系统中,支持SMT技术的CPU通常被视为拥有多个逻辑处理器,因此可以更加高效地利用处理器资源。
通过什么命令能看出当前cpu是否支持SMT:
可以通过Linux系统中的lscpu命令来查看当前CPU是否支持SMT技术。具体命令为:`lscpu | grep "Thread(s) per core"`。如果输出结果大于1,就表明该CPU支持SMT。输出结果为1则表示该CPU不支持SMT。
3.8.1 CPU Front-End //CPU前端
CPU前端和CPU北桥一个意思么?
不是一个意思。CPU前端是CPU核心的一部分,它包括指令预取、解码、分派等操作。CPU北桥则是芯片组的一部分,它连接了CPU、内存和其他扩展插槽,负责数据传输和协调各个组件之间的工作。
CPU前端由多个数据结构组成,其主要目的是高效地从内存中获取和解码指令。它的主要作用是将准备好的指令传送到CPU后端,后者负责实际执行指令。CPU前端每个周期从L1 I-cache中抓取16字节的x86指令。这些是复杂的、可变长度的x86指令,由两个线程共享。前端的预解码和解码阶段将这些复杂的x86指令转换为微操作(UOPs, 见第4.4节),并排队进入分配队列(IDQ)。
首先,预解码阶段通过检查指令确定并标记可变指令的边界。在x86中,指令长度可以从1字节到15字节不等。这个阶段还识别分支指令。预解码阶段最多将6个(也称为宏指令)指令移到两个线程之间分割的指令队列中。指令队列还支持一个宏指令融合单元,可以检测到两个宏指令可以合并为一个指令(见第4.4节)。这种优化可以节省管道的带宽。
每个时钟周期最多向解码器发送五个预解码指令。两个线程共享此接口,并且每个周期都可以访问接口。5路解码器将复杂的宏指令转换为固定长度的UOPs。
前端的一个主要性能提升特性是解码流缓存(DSB)或UOP缓存。其动机是将宏指令转换为UOPs的过程缓存到一个单独的结构(DSB)中,与L1 I-cache并行工作。在指令获取期间,还会检查DSB,以查看UOP转换是否已经可用。经常出现的宏指令会命中DSB,因此管道将避免针对16字节捆绑重复执行昂贵的预解码和解码操作。DSB提供了六个UOPs,与前段到后端接口的容量相匹配,并有助于维持整个核心的平衡。DSB与分支预测单元(BPU)协同工作。BPU预测所有分支指令的方向,并根据此预测引导下一条指令获取。
某些非常复杂的指令可能需要比解码器处理的UOPs更多的UOPs。这些指令的UOPs来自微码序列器(MSROM)。此类指令的示例包括支持硬件操作的字符串操作、加密、同步等。此外,MSROM保留了处理异常情况的微码操作,例如分支预测错误(需要清空管道)、浮点辅助等(例如指令使用异常的浮点值)。
指令解码队列(IDQ)为有序前端与乱序后端之间提供了接口。IDQ按顺序排列UOPs。IDQ共有128个UOPs,每个硬件线程64个UOPs。
3.8.2 CPU Back-End
CPU后端采用了一个乱序引擎,执行指令并存储结果。
CPU后端的核心是224个条目的重排缓冲区(ROB)。该单元处理数据依赖性。ROB将在调度/保留站单元中使用的物理寄存器映射到结构可见寄存器。ROB还提供了寄存器重命名和跟踪推测执行。ROB条目始终按程序顺序退役。
保留站/调度器(RS)是跟踪给定UOP所有资源可用性并在准备就绪后将其分派到分配的端口的结构。该核心是8道超标量。因此,RS可以每个周期分派多达8个UOPs。如图14所示,每个分派端口支持不同的操作:
• 端口0、1、5和6提供所有整数、FP和向量ALU。发往这些端口的UOPs不需要内存操作。
• 端口2和3用于地址生成和加载操作。
• 端口4用于存储操作。
• 端口7用于地址生成。
//端口2 3 4 7涉及内存操作
3.9 Performance Monitoring Unit
每个现代CPU都提供了监视性能的手段,这些手段被合并到性能监视单元(PMU)中。它包括帮助开发人员分析其应用程序性能的功能。现代英特尔CPU中PMU的一个示例如图15所示。大多数现代PMU都有一组性能监视计数器(PMC),可以用于收集程序执行过程中发生的各种性能事件。稍后在第5.3节中,我们将讨论如何使用PMCs进行性能分析。此外,还可能存在其他增强性能分析的功能,如LBR、PEBS和PT,整个第6章专门讨论这些内容。
Figure 15: Performance Monitoring Unit of a modern Intel CPU.
随着每个新一代CPU的设计不断演进,它们的PMUs也在不断发展。可以使用cpuid命令确定CPU中PMU的版本,如清单5所示。
每个Intel PMU版本的特征以及与前一个版本的变化可以在[Int,2020,Volume 3B,Chapter 18]中找到。
Listing 5 Querying your PMU
$ cpuid
...
Architecture Performance Monitoring Features (0xa/eax):
version ID = 0x4 (4)
number of counters per logical processor = 0x4 (4)
bit width of counter = 0x30 (48)
...
Architecture Performance Monitoring Features (0xa/edx):
number of fixed counters = 0x3 (3)
bit width of fixed counters = 0x30 (48)
...
3.9.1 Performance Monitoring Counters
如果我们想象一个处理器的简化视图,它可能看起来像图16所示的样子。正如本章前面所讨论的那样,现代CPU具有缓存、分支预测器、执行流水线和其他单元。当连接到多个单元时,PMC可以从中收集有趣的统计信息。例如,它可以计算经过了多少个时钟周期,执行了多少条指令,在这段时间内发生了多少次缓存未命中或分支预测错误等性能事件。
Figure 16: Simplifified view of a CPU with a performance monitoring counter.
通常,PMC的宽度为48位,这允许分析工具在不中断程序执行的情况下运行更长时间。性能计数器是作为模型特定寄存器(MSR)实现的硬件寄存器。这意味着计数器的数量和宽度可以因型号而异,并且您不能在CPU中依靠相同数量的计数器。应该首先查询使用工具如cpuid等计算机命令,以了解可用的性能计数器。
PMC可以通过RDMSR和WRMSR指令访问,但只能从内核空间执行这些指令。工程师们经常想要计算已执行的指令数量和经过的时钟周期,因此英特尔的PMU专门设有PMC来收集此类事件。英特尔的PMU具有固定的和可编程的PMC。固定计数器始终在CPU核心内测量相同的内容。对于可编程计数器,用户可以选择他们想要测量的内容。通常每个逻辑核心有四个完全可编程的计数器和三个固定功能的计数器。固定计数器通常设置为计数核时钟、参考时钟和已退役指令(更多细节请参见第4节有关这些指标的详细信息)。
PMU具有大量的性能事件并不罕见。图15仅显示了现代英特尔CPU上监控的所有性能事件中的一小部分。很容易注意到可用PMC的数量远小于性能事件的数量。不可能同时计数所有事件,但分析工具通过在程序执行期间在性能事件组之间进行复用来解决这个问题(详见第5.3.3节)。
英特尔CPU的完整性能事件列表可以在[Int,2020,Volume 3B,Chapter 19]中找到。对于ARM芯片来说,它没有那么严格的定义。供应商根据ARM架构实现核心,但性能事件在意义和支持的事件方面差异很大。