diff --git a/404.html b/404.html index e131690..cfa7f74 100644 --- a/404.html +++ b/404.html @@ -216,8 +216,9 @@ div.csl-indent {
随着技术的进步,计算机的形态产生了巨大的变化,从巨型机到小型机到个人电脑(Personal Computer,简称PC)再到智能手机,其基础元件从电子管到晶体管再到超大规模集成电路。虽然计算机的形态和应用场合千变万化,但从用户感知的应用软件到最底层的物理载体,计算机系统均呈现出层次化的结构,图2.1直观地展示了这些层次。
-
-图 2.1: 计算机系统的层次
+
++图 2.1: 计算机系统的层次 +
从上到下,计算机系统可分为四个层次,分别为应用软件、基础软件、硬件电路和物理载体。软件以指令形式运行在CPU硬件上,而指令系统介于软件和硬件之间,是软硬件交互的界面,有着非常关键的作用。软硬件本身的更新迭代速度很快,而指令系统则可以保持较长时间的稳定。有了稳定不变的指令系统界面,软件与硬件得到有效的隔离,并行发展。遵循同一指令系统的硬件可以运行为该指令系统设计的各种软件,比如X86计算机既可运行最新软件,也可运行30年前的软件;反之,为一个指令系统设计的软件可以运行在兼容这一指令系统的不同的硬件实现上,例如同样的操作系统和应用软件在AMD与Intel的CPU上都可以运行。
指令系统包括对指令功能、运行时环境(如存储管理机制和运行级别控制)等内容的定义,涉及软硬件交互的各个方面内容,这些内容将在后续章节一一展开介绍。
@@ -535,9 +540,11 @@ div.csl-indent {VLIW结构的最初思想是最大限度利用指令级并行(Instruction Level Parallelism,简称ILP),VLIW的一个超长指令字由多个互相不存在相关性(控制相关、数据相关等)的指令组成,可并行进行处理。VLIW可显著简化硬件实现,但增加了编译器的设计难度。
VLIW的思想最初由Josh Fisher于20世纪80年代初在耶鲁大学提出,Fisher随后离开耶鲁创立了Multiflow公司,并研制了TRACE系列VLIW处理器。后来Fisher和同样经历创业失败的Bob Rau加入了HP公司,并主导了HP在20世纪90年代的计算机结构研究。
同时,Intel在i860中实现了VLIW,这也奠定了随后两家公司在Itanium处理器上的合作关系,Itanium(IA-64)采用的EPIC结构的思想即来源于VLIW。
-
-图 2.2: RISC、CISC、VLIW指令编码特点
+
++图 2.2: RISC、CISC、VLIW指令编码特点 +
图2.2直观地给出了RISC、CISC、VLIW三种结构的指令编码。MIPS三种类型的指令内部位域分配不同,但总长度均为32位;X86则不同指令的长度都可能不同;IA-64则将三条41位定长指令合并为一条128位的“束”。
页式虚拟存储可使各进程运行在各自独立的虚拟地址空间中,并提供内存映射、公平的物理内存分配和共享虚拟内存等功能,是计算机系统发展过程中具有里程碑意义的一项技术。
下面分别介绍上述几种存储管理方式的基本方法。
段式存储管理的地址转换过程如图2.3所示。虚拟地址分为段号和段内偏移两部分,地址转换时根据段号检索段表,得到对应段的起始物理地址(由段长度和基址可得),再加上段内偏移,得到最终的物理地址。需要注意的是,段表中存有每个段的长度,若段内偏移超过该段长度,将被视为不合法地址。
-
-图 2.3: 段式存储管理的地址转换过程
+
++图 2.3: 段式存储管理的地址转换过程 +
段式存储中每段可配置不同的起始地址,但段内地址仍需要连续,当程序段占用空间较大时,仍然存在内存碎片等问题。
页式存储管理的地址转换过程如图2.4所示。虚拟地址分为虚拟页号和页内偏移两部分,地址转换时根据虚拟页号检索页表,得到对应的物理页号,与页内偏移组合得到最终的物理地址。
-
-图 2.4: 页式存储管理的地址转换过程
+
++图 2.4: 页式存储管理的地址转换过程 +
段页式管理结合了段式和页式的特点,其地址转换过程如图2.5所示,虚拟地址分为段号、虚拟页号和页内偏移三部分,地址转换时首先根据段号查询段表得到对应段的页表起始地址,再根据虚拟页号查询页表得到物理页号,与页内偏移组合得到最终的物理地址。段页式同样需要检查段地址的合法性。
-
-图 2.5: 段页式存储管理的地址转换过程
+
++图 2.5: 段页式存储管理的地址转换过程 +
虚拟化技术在服务器领域特别有用,一台物理主机可以支撑多台虚拟机,运行各自的系统。虚拟机不绑定底层硬件,可看作一个软件进程,因而部署起来非常灵活。虚拟机中同样要支持不同的运行级别,为了提高效率,硬件辅助虚拟化成为虚拟化发展的必然趋势。IBM System/370早在1970年就增加了硬件虚拟化支持;2005年以来,Intel和AMD也分别提出了硬件辅助虚拟化的扩展VT和SVM。ARM的AArch64架构也定义了硬件虚拟化支持方面的内容。这些指令系统在硬件虚拟化支持中引入了新的运行级别,用于运行虚拟机操作系统的核心态和用户态程序。
以LoongArch指令系统为例,其运行级别主要包括调试模式(Debug Mode)、主机模式(Host Mode)和客户机模式(Guest Mode)。主机模式和客户机模式又各自包含PLV0~PLV3四个权限等级,即具有Host-PLV0~Host-PLV3和Guest-PLV0~Guest-PLV3这8个运行级别。所有运行级别互相独立,即处理器在某一时刻只能存在于某一种运行级别中。处理器上电复位后处于Host-PLV0级,随后根据需要在不同运行级别之间转换。
不同运行级别可访问并控制的处理器资源不同,图2.6给出了这种对应关系的示意。其中调试模式下具有最高的优先级,可以访问并控制处理器中所有的资源;Host-PLV0模式下可以访问并控制处理器中除了用于调试功能外的所有其他资源;Guest-PLV0模式下只能访问部分处理器资源,如客户机控制状态寄存器;Host-PLV1/2/3和Guest-PLV1/2/3则只能访问更少的处理器资源。
-
-图 2.6: LoongArch各运行级别可访问控制处理器资源示意
+
++图 2.6: LoongArch各运行级别可访问控制处理器资源示意 +
处理器可访问的地址空间包括寄存器空间和系统内存空间。寄存器空间包括通用寄存器、专用寄存器和控制寄存器。寄存器空间通过编码于指令中的寄存器号寻址,系统内存空间通过访存指令中的访存地址寻址。
通用寄存器是处理器中最常用的存储单元,一个处理器周期可以同时读取多条指令需要的多个寄存器值。现代指令系统都定义了一定数量的通用寄存器供编译器进行充分的指令调度。针对浮点运算,通常还定义了浮点通用寄存器。表2.1给出了部分常见指令集中整数通用寄存器的数量。
-指令集 | 整数通用寄存器数 |
Itanium | 128 |
VAX | 16 |
ARMv8 | 31 |
PowerPC | 32 |
Alpha | 32(包括“zero”) |
SPARC | 32(包括“zero”) |
MIPS | 在mips16模式下为8,在32/64位模式下为32(包括“zero”) |
ARMv7 | 在16位Thumb 模式下为7,在32位模式下为14 |
X86 | 16/32位时为8, 64位时为16 |
LoongArch | 32(包括“zero”) |
LoongArch指令系统中定义了32个整数通用寄存器和32个浮点通用寄存器,其编号分别表示为$r0~$r31和$f0~$f31,其中$r0总是返回全0。
除了通用寄存器外,有的指令系统还会定义一些专用寄存器,仅用于某些专用指令或专用功能。如MIPS指令系统中定义的HI、LO寄存器就仅用于存放乘除法指令的运算结果。
控制寄存器用于控制指令执行的环境,比如是核心态还是用户态。其数量、功能和访问方式依据指令系统的定义各不相同。LoongArch指令系统中定义了一系列控制状态寄存器(Control Status Register,简称CSR),将在第3章介绍。
@@ -606,11 +671,61 @@ div.csl-indent {寄存器-寄存器型。在这种类型的指令系统中,每个操作数也由指令显式指定,但除了访存指令外的其他指令的操作数都只能是寄存器。
表2.2给出了四种类型的指令系统中执行C=A+B的指令序列,其中A、B、C为不同的内存地址,R1、R2等为通用寄存器。
-堆栈型 | 累加器型 | 寄存器-存储器型 | 寄存器-寄存器型 |
PUSH A | LOAD A | LOAD R1,A | LOAD R1,A |
PUSH B | ADD B | ADD R1,B | LOAD R2,B |
ADD | STORE C | STORE C,R1 | ADD R3,R1,R2 |
POP C | STORE C,R3 |
寄存器-寄存器型指令系统中的运算指令的操作数只能来自寄存器,不能来自存储器,所有的访存都必须显式通过load和store指令来完成,所以寄存器-寄存器型又被称为load-store型。
早期的计算机经常使用堆栈型和累加器型指令系统,主要目的是降低硬件实现的复杂度。除了X86还保留堆栈型和累加器型指令系统外,当今的指令系统主要是寄存器型,并且是寄存器-寄存器型。使用寄存器的优势在于,寄存器的访问速度快,便于编译器的调度优化,并可以充分利用局部性原理,大量的操作可以在寄存器中完成。此外,寄存器-寄存器型的另一个优势是寄存器之间的相关性容易判断,容易实现流水线、多发射和乱序执行等方法。
计算机中常见的数据类型包括整数、实数、字符,数据长度包括1字节、2字节、4字节和8字节。X86指令集中还包括专门的十进制类型BCD。表2.3给出C语言整数类型与不同指令集中定义的名称和数据长度(以字节为单位)的关系。
-C语言名称 | LA32名称/数据长度1 | LA64名称/数据长度1 | X86名称/数据长度 | X86-64名称/数据长度 |
char | Byte/1 | Byte/1 | Byte/1 | Byte/1 |
short | Halfword/2 | Halfword/2 | Word/2 | Word/2 |
int | Word/4 | Word/4 | Dword/4 | Dword/4 |
long | Word/4 | Dword/8 | Dword/4 | Qword/8 |
long long | Dword/8 | Dword/8 | Qword/8 | Qword/8 |
1LA32和LA64分别是32位和64位LoongArch指令集 | ||||
实数类型在计算机中表示为浮点类型,包括单精度浮点数和双精度浮点数,单精度浮点数据长度为4字节,双精度浮点数据长度为8字节。
在指令中表达数据类型有两种方法。一种是由指令操作码来区分不同类型,例如加法指令包括定点加法指令、单精度浮点加法指令、双精度浮点加法指令。另一种是将不同类型的标记附在数据上,例如加法使用统一的操作码,用专门的标记来标明加法操作的数据类型。
寻址方式指如何在指令中表示要访问的内存地址。表2.4列出了计算机中常用的寻址方式,其中数组mem表示存储器,数组regs表示寄存器,mem[regs[Rn]]表示由寄存器Rn的值作为存储器地址所访问的存储器值。
-寻址方式 | 格式 | 含义 |
寄存器寻址(Register) | ADD R1,R2 | regs[R1]=regs[R1]+regs[R2] |
立即数寻址(Immediate) | ADD R1,#2 | regs[R1]=regs[R1]+2 |
偏移量寻址(Displacement) | ADD R1,100(R2) | regs[R1]=regs[R1]+mem[100+regs[R2]] |
寄存器间接寻址(Reg.Indirect) | ADD R1,(R2) | regs[R1]=regs[R1]+mem[regs[R2]] |
变址寻址(Indexed) | ADD R1,(R2+R3) | regs[R1]=regs[R1]+mem[regs[R2]+regs[R3]] |
绝对寻址(Absolute) | ADD R1,(100) | regs[R1]=regs[R1]+mem[100] |
存储器间接寻址(Mem.Indirect) | ADD R1,@(R2) | regs[R1]=regs[R1]+mem[mem[regs[R2]]] |
自增量寻址(Autoincrement) | ADD R1,(R2)+ | regs[R1]=regs[R1]+mem[regs[R2]],regs[R2]=regs[R2]+d |
自减量寻址(Autodecrement) | ADD R1,-(R2) | regs[R2]=regs[R2]-d,regs[R1]=regs[R1]+mem[regs[R2]] |
比例变址寻址(Scaled) | ADD R1,100(R2)(R3) | regs[R1]=regs[R1]+mem[100+regs[R2]+regs[R3]*d] |
除表2.4之外还可以列出很多其他寻址方式,但常用的寻址方式并不多。John L.Hennessy在其经典名著《计算机系统结构:量化研究方法(第二版)》中给出了如表2.5所示的数据,他在VAX计算机(VAX机的寻址方式比较丰富)上对SPEC CPU 1989中tex、spice和gcc这三个应用的寻址方式进行了统计。
-寻址方式 | tex | spice | gcc |
偏移量寻址 | 32% | 55% | 40% |
立即数寻址 | 43% | 17% | 39% |
寄存器间接寻址 | 24% | 3% | 11% |
自增量寻址 | 0% | 16% | 6% |
存储器间接寻址 | 1% | 6% | 1% |
从表2.5可以看出,偏移量寻址、立即数寻址和寄存器间接寻址是最常用的寻址方式,而寄存器间接寻址相当于偏移量为0的偏移量寻址。因此,一个指令系统至少应支持寄存器寻址、立即数寻址和偏移量寻址。经典的RISC指令集,如MIPS和Alpha,主要支持上述三种寻址方式以兼顾硬件设计的简洁和寻址计算的高效。不过随着工艺和设计水平的提升,现代商用RISC类指令集也逐步增加所支持的寻址方式以进一步提升代码密度,如64位的LoongArch指令集(简称LA64)就在寄存器寻址、立即数寻址和偏移量寻址基础之上还支持变址寻址方式。
在四类指令中,转移指令的行为较为特殊,值得详细介绍。转移指令包括条件转移、无条件转移、过程调用和过程返回等类型。转移条件和转移目标地址是转移指令的两个要素,两者的组合构成了不同的转移指令:条件转移要判断条件再决定是否转移,无条件转移则无须判断条件;相对转移是程序计数器(PC)加上一个偏移量作为转移目标地址,绝对转移则直接给出转移目标地址;直接转移的转移目标地址可直接由指令得到,间接转移的转移目标地址则需要由寄存器的内容得到。程序中的switch语句、函数指针、虚函数调用和过程返回都属于间接转移。由于取指译码时不知道目标地址,因此硬件结构设计时处理间接跳转比较麻烦。
转移指令有几个特点:第一,条件转移在转移指令中最常用;第二,条件转移通常只在转移指令附近进行跳转,偏移量一般不超过16位;第三,转移条件判定比较简单,通常只是两个数的比较。条件转移指令的条件判断通常有两种实现方式:采用专用标志位和直接比较寄存器。采用专用标志位方式的,通过比较指令或其他运算指令将条件判断结果写入专用标志寄存器中,条件转移指令仅根据专用标志寄存器中的判断结果决定是否跳转。采用直接比较寄存器方式的,条件转移指令直接对来自寄存器的数值进行比较,并根据比较结果决定是否进行跳转。X86和ARM等指令集采用专用标志位方式,RISC-V指令集则采用直接比较寄存器方式,MIPS和LoongArch指令集中的整数条件转移指令采用直接比较寄存器方式,而浮点条件转移指令则采用专用标志位方式。
指令编码就是操作数和操作码在整个指令码中的摆放方式。CISC指令系统的指令码长度可变,其编码也比较自由,可依据类似于赫夫曼(Huffman)编码的方式将操作码平均长度缩小。RISC指令系统的指令码长度固定,因此需要合理定义来保证各指令码能存放所需的操作码、寄存器号、立即数等元素。图2.7给出了LoongArch指令集的编码格式。
-
-图 2.7: LoongArch指令集的编码格式
+
++图 2.7: LoongArch指令集的编码格式 +
如图2.7所示,32位的指令编码被划分为若干个区域,按照划分方式的不同共包含9种典型的编码格式,即3种不含立即数的格式2R、3R、4R和6种包含立即数的格式2RI8、2RI12、2RI14、2RI16、1RI21和I26。编码中的opcode域用于存放指令的操作码;rd、rj、rk和ra域用于存放寄存器号,通常rd表示目的操作数寄存器,而rj、rk、ra表示源操作数寄存器;Ixx域用于存放指令立即数,即立即数寻址方式下指令中给出的数。指令中的立即数不仅作为运算型指令的源操作数,也作为load/store指令中相对于基地址的地址偏移以及转移指令中转移目标的偏移量。
本节以MIPS、PA-RISC、PowerPC、SPARC v9和LoongArch为例,比较不同RISC指令系统的指令格式、寻址模式和指令功能,以加深对RISC的了解。
五种RISC指令集的指令格式如图2.8所示。在寄存器类指令中,操作码都由操作码(OP)和辅助操作码(OPX)组成,操作数都包括两个源操作数(RS)和一个目标操作数(RD);立即数类指令都由操作码、源操作数、目标操作数和立即数(Const)组成,立即数的位数各有不同;跳转类指令大同小异,PA-RISC与其他四种差别较大。总的来说,五种RISC指令集的指令编码主要组成元素基本相同,只是在具体摆放位置上存在差别。
-
-### 寻址方式比较
五种指令集的寻址方式如表2.6所示。MIPS、SPARC和LoongArch只支持四种常用的寻址方式,PowerPC和PA-RISC支持的寻址方式较多。
-五种RISC指令集的指令格式如图2.8所示。在寄存器类指令中,操作码都由操作码(OP)和辅助操作码(OPX)组成,操作数都包括两个源操作数(RS)和一个目标操作数(RD);立即数类指令都由操作码、源操作数、目标操作数和立即数(Const)组成,立即数的位数各有不同;跳转类指令大同小异,PA-RISC与其他四种差别较大。总的来说,五种RISC指令集的指令编码主要组成元素基本相同,只是在具体摆放位置上存在差别。
+
++图 2.8: 五种RISC指令集的指令编码格式 +
五种指令集的寻址方式如表2.6所示。MIPS、SPARC和LoongArch只支持四种常用的寻址方式,PowerPC和PA-RISC支持的寻址方式较多。
+寻址方式 | MIPS | PowerPC | PA-RISC | SPARC | LoongArch |
寄存器寻址 | Y | Y | Y | Y | Y |
立即数寻址 | Y | Y | Y | Y | Y |
偏移量寻址 | Y | Y | Y | Y | Y |
变址寻址 | Y(仅浮点) | Y | Y | Y | Y |
比例变址寻址 | Y | ||||
自增/自减+偏移量寻址 | Y | Y | |||
自增/自减+变址寻址 | Y | Y |
注:表2.6中Y表示支持该寻址方式。
RISC指令集都有一些公共指令,如load-store、算术运算、逻辑运算和控制流指令。不同指令集在比较和转移指令上区别较大。
1)load-store指令。load指令将内存中的数据取入通用寄存器,store指令将通用寄存器中的数据存至内存中。表2.7给出了LoongArch指令集的load-store指令实例。当从内存中取回的数据位宽小于通用寄存器位宽时,后缀没有U的指令进行有符号扩展,即用取回数据的最高位(符号位)填充目标寄存器的高位,否则进行无符号扩展,即用数0填充目标寄存器的高位。
-指令 | 指令功能 |
LD.B | 取字节 |
LD.BU | 取字节,无符号扩展 |
LD.H | 取半字 |
LD.HU | 取半字,无符号扩展 |
LD.W | 取字 |
LD.WU | 取字,无符号扩展 |
LD.D | 取双字 |
ST.B | 存字节 |
ST.H | 存半字 |
ST.W | 存字 |
ST.D | 存双字 |
2)ALU指令。ALU指令都是寄存器型的,常见的ALU指令包括加、减、乘、除、与、或、异或、移位和比较等。表2.8为LoongArch指令集的ALU指令实例。其中带有“.W”后缀的指令操作的数据位宽为32位(字),带有“.D”后缀的指令操作的数据位宽为64位(双字)。
-指令 | 指令功能 |
ADD.W | 字加 |
ADDI.W | 字加立即数 |
SUB.W | 字减 |
ADD.D | 双字加 |
ADDI.D | 双字加立即数 |
SUB.D | 双字减 |
SLT | 有符号数比较小于置1 |
SLTI | 有符号数立即数比较小于置1 |
SLTU | 无符号数比较小于置1 |
SLTUI | 无符号数立即数比较小于置1 |
AND | 与 |
OR | 或 |
XOR | 异或 |
NOR | 或非 |
ANDI | 与立即数 |
ORI | 或立即数 |
XORI | 异或立即数 |
LU12I.W | 加载20位立即数到高位 |
SLL.W | 字逻辑左移变量位 |
SRL.W | 字逻辑右移变量位 |
SRA.W | 字算术右移变量位 |
SLLI.W | 字逻辑左移常量位 |
SRLI.W | 字逻辑右移常量位 |
SRAI.W | 字算术右移常量位 |
SLL.D | 双字逻辑左移变量位 |
SRL.D | 双字逻辑右移变量位 |
SRA.D | 双字算术右移变量位 |
SLLI.D | 双字逻辑左移常量位 |
SRLI.D | 双字逻辑右移常量位 |
SRAI.D | 双字算术右移常量位 |
MUL.W | 字乘取低半部分 |
MULH.W | 有符号字乘取高半部分 |
MULH.WU | 无符号字乘取高半部分 |
MUL.D | 双字乘取低半部分 |
MULH.D | 有符号双字乘取高半部分 |
MULH.DU | 无符号双字乘取高半部分 |
DIV.W | 有符号字除取商 |
DIV.WU | 无符号字除取商 |
MOD.W | 有符号字除取余 |
MOD.WU | 无符号字除取余 |
DIV.D | 有符号双字除取商 |
DIV.DU | 无符号双字除取商 |
MOD.D | 有符号双字除取余 |
MOD.DU | 无符号双字除取余 |
3)控制流指令。控制流指令分为绝对转移指令和相对转移指令。相对转移的目标地址是当前的PC值加上指令中的偏移量立即数;绝对转移的目标地址由寄存器或指令中的立即数给出。表2.9为LoongArch指令集中控制流指令的实例。
-指令 | 指令功能 |
JIRL | 相对寄存器偏移跳转并链接 |
B | 无条件相对转移 |
BL | 无条件相对转移并链接 |
BEQ | 等于时相对转移 |
BNE | 不等时相对转移 |
BLT | 有符号比较小于时相对转移 |
BGE | 有符号比较大于等于时相对转移 |
BLTU | 无符号比较小于时相对转移 |
BGEU | 无符号比较大于等于时相对转移 |
BEQZ | 等于0相对转移 |
BNEZ | 不等于0时相对转移 |
在条件转移指令中,转移条件的确定有两种方式:判断条件码和比较寄存器的值。SPARC采用条件码的方式,整数运算指令置条件码,条件转移指令使用条件码进行判断。MIPS和LoongArch的定点转移指令使用寄存器比较的方式进行条件判断,而浮点转移指令使用条件码。PowerPC中包含一个条件寄存器,条件转移指令指定条件寄存器中的特定位作为跳转条件。PA-RISC有多种选择,通常通过比较两个寄存器的值来决定是否跳转。
RISC指令集中很多条件转移采用了转移延迟槽(Delay Slot)技术,程序中条件转移指令的后一条指令为转移延迟槽指令。在早期的静态流水线中,条件转移指令在译码时,后一条指令即进入取指流水级。为避免流水线效率的浪费,有些指令集规定转移延迟槽指令无论是否跳转都要执行。MIPS、SPARC和PA-RISC都实现了延迟槽,但对延迟槽指令是否一定执行有不同的规定。对于当今常用的动态流水线和多发射技术而言,延迟槽技术则没有使用的必要,反而成为指令流水线实现时需要特殊考虑的负担。Alpha、PowerPC和LoongArch均没有采用转移延迟槽技术。
除了上述公共功能外,不同的RISC指令集经过多年的发展形成了各自的特色,下面举例介绍其各自的主要特色。
1)MIPS部分指令特色。前面介绍过访存地址的对齐问题,当确实需要使用不对齐数据时,采用对齐访存指令就需要复杂的地址计算、移位和拼接等操作,这会给大量使用不对齐访存的程序带来明显的代价。MIPS指令集实现了不对齐访存指令LWL/LWR。LWL指令读取访存地址所在的字并将访存地址到该字中最低位的字节拼接到目标寄存器的高位,LWR指令读取访存地址所在的字并将访存地址到该字中最高位的字节拼接到目标寄存器的低位。上述字中的最低位和最高位字节会根据系统采用的尾端而变化,不同尾端下,LWL和LWR的作用相反。例如,要加载地址1至4的内容到R1寄存器,不同尾端的指令和效果如图2.9所示。
-
-图 2.9: 不同尾端下的LWL/LWR指令效果
+
++图 2.9: 不同尾端下的LWL/LWR指令效果 +
LWL和LWR指令设计巧妙,兼顾了使用的便利性和硬件实现的简单性,是MIPS指令集中比较有特色的指令。
2)SPARC部分指令特色。SPARC指令系统有很多特色,这里挑选寄存器窗口进行介绍。在SPARC指令系统中,一组寄存器(SPARC v9中规定为8~31号寄存器)可用于构成窗口,窗口可有多个,0~7号寄存器作为全局寄存器。寄存器窗口的好处在于函数调用时可不用保存现场,只需切换寄存器组。
3)PA-RISC部分指令特色。PA-RISC指令集最大的特色就是Nullification指令,除了条件转移指令,其他指令也可以根据执行结果确定下一条指令是否执行。例如ADDBF(add and branch if false)指令在完成加法后,检查加法结果是否满足条件,如果不满足就进行转移。一些简单的条件判断可以用Nullification指令实现。
4)PowerPC部分指令特色。在RISC结构中,PowerPC的寻址方式、指令格式和转移指令都是最多的,甚至支持十进制运算,因此又被称为“RISC中的CISC”。表2.10给出了分别用PowerPC指令和Alpha指令实现的简单程序示例。实现同样的循环程序,PowerPC只需要6条指令,Alpha则需要10条指令,原因就在于PowerPC的指令功能较强。例如其中的LFU(load with update)和STFU(store with update)指令,除了访存外还能自动修改基址寄存器的值;FMADD可以在一条指令中完成乘法和加法;转移指令BC可同时完成计数值减1和条件转移。
-源代码:for(k=0;k<512;k++) x[k]=r*x[k]+t*y[k]; | |
PowerPC代码 | Alpha代码 |
r3+8指向x | r1指向x |
LOOP: | LOOP: |
默认情况下,通用寄存器$r4~$r11(记为$a0~$a7)作为参数输入,其中$r4和$r5同时也作为返回值,通用寄存器$r12~$r20(记为$t0~$t8)作为子程序的暂存器无须存储和恢复。LoongArch中没有专门的栈结构和栈指针,通用寄存器$r3(记为$sp)通常作为栈指针寄存器,指向栈顶。
一个简单的C语言过程调用程序及其LoongArch汇编码如表2.11所示。
-C代码 | LoongArch汇编 |
int add(int a,int b) | add: |
ref程序是add程序的调用者,通过BL指令进行调用,BL指令会修改\(ra寄存器的值,因此在ref中需要将\)ra寄存器的值保存到栈中,栈顶指针和RA值存放的位置遵循LoongArch函数调用规范,这部分内容将在4.1节中进行介绍。add程序的返回值放在$a0寄存器中,这同时也是ref程序的返回值,因此无须进行更多搬运。
C语言中的控制流语句共有9种,可分为三类:辅助控制语句、选择语句、循环语句,如表2.12所示。
-控制流语句 | 选择语句 | if ~ else |
switch ~ case | ||
循环语句 | for | |
while | ||
do ~ while | ||
辅助控制语句 | break | |
continue | ||
goto | ||
return |
(1)辅助控制语句
goto语句无条件地跳转到程序中某标号处,其作用与无条件相对跳转指令相同,在LoongArch指令集中表示为B指令跳转到一个标号。break、continue语句的作用与goto类似,只是跳转的标号位置不同。return语句将过程中的变量作为返回值并直接返回,在编译器中对应于返回值写入和返回操作。
(2)选择语句
if~else语句及其对应的LoongArch汇编码如表2.13所示。
-C代码 | LoongArch汇编 |
if (cond_exp) | move $t0, cond_exp |
这里的if ~ else实现采用了BEQZ指令,当$t0寄存器的值等于0时进行跳转,跳转到标号.L1执行“else”分支中的操作,当$t0寄存器的值不等于0时,则顺序执行“then”分支中的操作并在完成后无条件跳转到标号.L2处绕开“else”分支。
switch ~ case语句的结构更为复杂,由于可能的分支数较多,通常会被映射为跳转表的形式,如表2.14所示。如果在编译选项中加入-fno-jump-tables的选项,那么switch ~ case语句还可以被映射为跳转级联的形式,如表2.15所示。
-C代码 | LoongArch汇编 |
int st(int a, int b, int c) | st: |
1alsl.d rd, rj, rk, sa所进行的操作是:GR[rd]=(GR[rj]<<sa)+GR[rk]。即将rj号通用寄存器中的值先左移sa位再与rk号通用寄存器中的值相加,结果写入rd号通用寄存器中。 | |
C代码 | LoongArch汇编 |
int st(int a, int b, int c) | st: |
在这个例子中,$t0寄存器存放各case分支的值并依次与第一个参数a(存放在$a0寄存器中)进行比较,根据比较的结果分别跳转到指定标号。读者可自行分析各case分支的执行流。通过比较表2.14和2.15中的汇编代码可以看到,在case分支较多时,采用跳转表实现有助于减少级联的转移指令。
循环语句均可映射为条件跳转指令,与选择语句的区别就在于跳转的目标标号在程序段已执行过的位置(backward)。三种循环语句的C语言及其对应的LoongArch汇编码如表2.16所示。
-C代码 | LoongArch汇编 |
int test_for(int a) { | test_for: |
1)运行模式定义及其转换
现代计算机的操作系统都实现了保护模式,至少需要用户态和核心态两种运行模式。应用运行在用户态模式下,操作系统运行在核心态模式下。因此,指令系统必须有相应的运行模式以做区分。比如MIPS定义了user、supervisor、kernel三种模式,X86定义了Ring0~Ring3四种模式,LoongArch定义了PLV0~PLV3四种模式。
刚开机时,CPU初始化为操作系统核心态对应的运行模式,执行引导程序加载操作系统。操作系统做完一系列初始化后,控制CPU切换到操作系统用户态对应的运行模式去执行应用程序。应用程序执行过程中,如果出现用户态对应的运行模式无法处理的事件,则CPU会通过异常或中断回到核心态对应的运行模式,执行操作系统提供的服务程序。操作系统完成处理后再控制CPU返回用户态对应的运行模式,继续运行原来的应用程序或者调度另一个应用程序。在LoongArch指令系统中,CPU当前所处的运行模式由当前模式信息控制状态寄存器(CSR.CRMD)的PLV域的值确定,其值为0~3分别表示CPU正处于PLV0~PLV3四种运行模式(见图3.1)。
-
-图 3.1: LoongArch当前模式信息控制状态寄存器格式
+
++图 3.1: LoongArch当前模式信息控制状态寄存器格式 +
运行模式的转换过程与虚拟存储和异常中断紧密相关,共同构建出完备的保护模式。不少指令系统还支持虚拟机模式、调试模式等,使计算机系统更为易用。
2)虚拟存储管理
@@ -514,11 +519,61 @@ div.csl-indent {3.2节将对异常与中断做更详细的介绍。
4)控制状态寄存器
控制状态寄存器位于一个独立的地址空间,是支撑前面3种机制的具体实现,不同的指令系统差别较大。下面以LoongArch指令系统为例,列出其控制状态寄存器的功能。
-助记符 | 编号 | 说明 |
CRMD | Ox0 | 处理器当前运行模式及地址翻译模式、全局中断使能等配置信息。 |
PRMD | 0x1 | 触发当前普通异常的现场的运行模式及全局中断使能等配置信息 |
EUEN | 0x2 | 扩展部件的使能控制 |
MISC | 0x3 | 各权限等级下是否运行使用部分特权指令等杂项配置 |
ECFG | 0x4 | 局部中断使能、异常入口间距等配置信息 |
ESTAT | 0x5 | 记录异常和中断发生原因 |
ERA | 0x6 | 普通异常处理返回地址 |
BADV | 0x7 | 记录触发地址相关异常的访存虚地址 |
BADI | 0x8 | 记录触发异常指令的指令编码 |
EENTRY | 0xC | 配置普通异常处理程序入口地址 |
TLBIDX | 0x10 | 存储管理(TLB)相关寄存器,将在第3节进行详细介绍 |
TLBEHI | 0x11 | |
TLBELO0 | 0x12 | |
TLBELO1 | 0x13 | |
ASID | 0x18 | |
STLBPS | 0x1E | |
PGDL | 0x19 | |
PGDH | 0x1A | |
PGD | 0x1B | |
PWCL | 0x1C | |
PWCH | 0x1D | |
SAVEn | 0x30+n | 保存临时数据 |
TID | 0x40 | 恒定频率计时器和定时器相关寄存器 |
TCFG | 0x41 | |
TVAL | 0x42 | |
CNTC | 0x43 | |
TICLR | 0x44 | |
LLBCTL | 0x60 | LLBit的控制 |
TLBRENTRY | 0x88 | TLB重填异常处理专用寄存器 |
TLRBBADV | 0x89 | |
TLBERA | 0x8A | |
TLBRSAVE | 0x8B | |
TLBRELO0 | 0x8C | |
TLBRELO1 | 0x8D | |
TLBREHI | 0x8E | |
TLBRPRMD | 0x8F | |
MERRCTL | 0x90 | 由Cache校验错所引发的机器错误异常的相关控制状态寄存器 |
MERRINFO1 | 0x91 | |
MERRINFO2 | 0x92 | |
MERRENTRY | 0x93 | |
MERRERA | 0x94 | |
MERRSAVE | 0x95 | |
DMW0~DMW3 | 0x180~0x183 | 直接映射配置窗口0~3的配置寄存器 |
DBG | 0x500 | 调试相关的控制状态寄存器 |
DERA | 0x501 | |
DSAVE | 0x502 |
控制状态寄存器虽然重要,但对其操作的频率通常远远低于通用寄存器,所以指令系统中通常不会设计针对控制状态寄存器的访存和复杂运算指令。不过大多数指令系统至少会定义若干在控制状态寄存器和通用寄存器之间进行数据搬运的指令,从而可以将数据移动到通用寄存器中进行相关处理,或者进一步将处理结果写回控制状态寄存器中。在LoongArch指令系统中,就定义了CSRRD和CSRWR指令来完成控制状态寄存器的读写操作。例如,指令“csrrd $t0, CSR_CRMD3”将控制状态寄存器CRMD的值读出,然后写入通用寄存器$t0中;指令“csrwr $t0, CSR_CRMD”将通用寄存器$t0中的值写入到控制状态寄存器CRMD中,同时将控制状态寄存器CRMD的旧值写入通用寄存器$t0中。
5)系统调用和陷入:由专有指令产生,其目的是产生操作系统可识别的异常,用于在保护模式下调用核心态的相关操作。
6)需要软件修正的运算:常见的是浮点指令导致的异常,某些操作和操作数的组合硬件由于实现过于复杂而不愿意处理,寻求软件的帮助。
下表列举了LoongArch指令系统中主要的异常。
-异常代号 | 异常编号 | 异常说明 | 所属异常类别 | |
Ecode | Esubcode | |||
PIL | 0x1 | load操作页无效异常 | 地址转换异常 | |
PIS | 0x2 | store操作页无效异常 | 地址转换异常 | |
PIF | 0x3 | 取指操作页无效异常 | 地址转换异常 | |
PME | 0x4 | 页修改异常 | 地址转换异常 | |
PNR | 0x5 | 页不可读异常 | 地址转换异常 | |
PNX | 0x6 | 页不可执行异常 | 地址转换异常 | |
PPI | 0x7 | 页权限等级不合规异常 | 地址转换异常 | |
ADEF | 0x8 | 0x0 | 取指地址错异常 | 指令执行中的错误 |
ADEM | 0x1 | 访存指令地址错异常 | 指令执行中的错误 | |
ALE | 0x9 | 地址非对齐异常 | 指令执行中的错误 | |
BCE | 0xA | 边界约束检查错异常 | 指令执行中的错误 | |
SYS | 0xB | 系统调用异常 | 系统调用和陷入 | |
BRK | 0xC | 断点异常 | 系统调用和陷入 | |
INE | 0xD | 指令不存在异常 | 指令执行中的错误 | |
IPE | 0xE | 指令权限等级错异常 | 指令执行中的错误 | |
FPD | 0xF | 浮点指令未使能异常 | 系统调用和陷入 | |
SXD | 0x10 | 128位向量扩展指令未使能异常 | 系统调用和陷入 | |
ASXD | 0x11 | 256位向量扩展指令未使能异常 | 系统调用和陷入 | |
FPE | 0x12 | 0x0 | 基础浮点指令异常 | 需要软件修正的运算 |
VFPE | 0x1 | 向量浮点指令异常 | 需要软件修正的运算 | |
WPEF | 0x13 | 0x0 | 取指监测点异常 | 系统调用和陷入 |
WPEM | 0x1 | load/store操作监测点异常 | 系统调用和陷入 | |
INT | 中断 | 外部事件 | ||
TLBR | TLB重填异常 | 地址转换异常 | ||
MERR | 机器错误异常 | 数据完整性问题 | ||
异常处理的流程包括异常处理准备、确定异常来源、保存执行状态、处理异常、恢复执行状态并返回等。主要内容是确定并处理异常,同时正确维护上下文环境。异常处理是一个软硬件协同的过程,通常CPU硬件需要维护一系列控制状态寄存器(域)以用于软硬件之间的交互。LoongArch指令系统中与异常(含中断)处理相关的控制状态寄存器格式如图3.2所示。
-
-图 3.2: LoongArch异常处理相关控制状态寄存器
+
++图 3.2: LoongArch异常处理相关控制状态寄存器 +
下面对异常处理流程的五个阶段进行介绍。
1)异常处理准备。当异常发生时,CPU在转而执行异常处理前,硬件需要进行一系列准备工作。
@@ -621,9 +728,11 @@ div.csl-indent {4)节约物理内存:程序可以通过合理的映射来节约物理内存。当操作系统中有相同程序的多个副本在同时运行时,让这些副本使用相同的程序代码和只读数据是很直观的空间优化措施,而通过存储管理可以轻松完成这些。此外,在运行大型程序时,操作系统无须将该程序所需的所有内存都分配好,而是在确实需要使用特定页时再通过存储管理的相关异常处理来进行分配,这种方法不但节约了物理内存,还能提高程序初次加载的速度。
页式存储管理是一种常见而高效的方式,操作系统将内存空间分为若干个固定大小的页,并维护虚拟页地址和物理页地址的映射关系(即页表)。页大小涉及页分配的粒度和页表所占空间,目前的操作系统常用4KB的页。此时,虚拟内存地址可表示为虚拟页地址和页内偏移两部分,在进行地址转换时通过查表的方式将虚拟页地址替换为物理页地址就可得到对应的物理内存地址。
在32位系统中,采用4KB页时,单个完整页表需要1M项,对每个进程维护页表需要相当可观的空间代价,因此页表只能放在内存中。若每次进行地址转换时都需要先查询内存,则会对性能产生明显的影响。为了提高页表访问的速度,现代处理器中通常包含一个转换后援缓冲器(Translation Lookaside Buffer,简称TLB)来实现快速的虚实地址转换。TLB也称页表缓存或快表,借由局部性原理,存储当前处理器中最经常访问页的页表。一般TLB访问与Cache访问同时进行,而TLB也可以被视为页表的Cache。TLB中存储的内容包括虚拟地址、物理地址和保护位,可分别对应于Cache的Tag、Data和状态位。包含TLB的地址转换过程如图3.3所示。
-
-图 3.3: 包含TLB的地址转换过程
+
++图 3.3: 包含TLB的地址转换过程 +
处理器用地址空间标识符(Address Space Identifier,简称ASID)和虚拟页号(Virtual Page Number,简称VPN)在TLB中进行查找匹配,若命中则读出其中的物理页号(Physical Page Number,简称PPN)和标志位(Flag)。标志位用于判断该访问是否合法,一般包括是否可读、是否可写、是否可执行等,若非法则发出非法访问异常;物理页号用于和页内偏移(Offset)拼接组成物理地址。若未在TLB中命中,则需要将页表内容从内存中取出并填入TLB中,这一过程通常称为TLB重填(TLB Refill)。TLB重填可由硬件或软件进行,例如X86、ARM处理器采用硬件TLB重填,即由硬件完成页表遍历(Page Table Walker),将所需的页表项填入TLB中;而MIPS、LoongArch处理器默认采用软件TLB重填,即查找TLB发现不命中时,将触发TLB重填异常,由异常处理程序进行页表遍历并进行TLB填入。
在计算机中,外存、内存、Cache、通用寄存器可以组织成速度由慢到快的存储层次。TLB在存储层次中的位置和作用与Cache类似,可视为页表这种特殊内存数据的专用Cache。
@@ -643,9 +752,11 @@ div.csl-indent {页表映射模式存储管理的核心部件是TLB。LoongArch指令系统下TLB分为两个部分,一个是所有表项的页大小相同的单一页大小TLB(Singular-Page-Size TLB,简称STLB),另一个是支持不同表项的页大小可以不同的多重页大小TLB(Multiple-Page-Size TLB,简称MTLB)。STLB的页大小可通过STLBPS控制寄存器进行配置。
在虚实地址转换过程中,STLB和MTLB同时查找。相应地,软件需保证不会出现MTLB和STLB同时命中的情况,否则处理器行为将不可知。MTLB采用全相联查找表的组织形式,STLB采用多路组相联的组织形式。对于STLB,如果其有2INDEX组,且配置的页大小为2PS字节,那么硬件查询STLB的过程中,是将虚地址的\[PS+INDEX:PS\]位作为索引值来访问各路信息的。接下来介绍LoongArch64指令系统中TLB单个表项的结构,如图3.4所示。
-
-图 3.4: LoongArch64指令系统中TLB表项结构
+
++图 3.4: LoongArch64指令系统中TLB表项结构 +
在TLB表项中,E表示该TLB表项是否存在,E为0的项在进行TLB查找时将被视为无效项;ASID标记该TLB表项属于哪个地址空间,只有CPU中当前的ASID(由CSR.ASID的ASID域决定)与该域相同时才能命中,ASID用于区分不同进程的页表;G位域表示全局域,为1时关闭ASID匹配,表示该TLB表项适用于所有的地址空间;PS表示该页表项中存放的页大小,数值是页大小的2的幂指数,有6比特宽,因此LoongArch指令系统的页大小理论上可以任意变化,处理器可以实现其中的一段范围;VPPN表示虚双页号,在LoongArch指令系统中,TLB的每项把两个连续的虚拟页映射为两个物理页;PPN为物理页号,这个域的实际有效宽度取决于该处理器支持的物理内存空间的大小;PLV表示该页表项对应的权限等级;RPLV为受限权限等级使能,当RPLV=0时,该页表项可以被任何权限等级不低于PLV的程序访问,否则,该页表项仅可以被权限等级等于PLV的程序访问;MAT控制落在该页表项所在地址空间上的访存操作的存储访问类型,如是否可通过Cache缓存等;NX为不可执行位,为1表示该页表项所在地址空间上不允许执行取指操作;NR为不可读位,为1表示该页表项所在地址空间上不允许执行load操作;D被称为“脏”(Dirty)位,为1表示该页表项所对应的地址范围内已有脏数据;V为有效位,为1表明该页表项是有效且被访问过的。
除了上面提到的TLB查找操作外,LoongArch指令系统中定义了一系列用于访问和控制TLB的控制状态寄存器,用于TLB内容的维护操作。
LoongArch指令系统中用于访问和控制TLB的控制状态寄存器大致可以分为三类:第一类用于非TLB重填异常处理场景下的TLB访问和控制,包括TLBIDX、TLBEHI、TLBELO0、TLBELO1、ASID和BADV;第二类用于TLB重填异常处理场景,包括此场景下TLB访问控制专用的TLBREHI、TLBRELO0、TLBRELO1和TLBRBADV以及此场景下保存上下文专用的TLBRPRMD、TLBRERA和TLBRSAVE;第三类用于控制页表遍历过程,包括PGDL、PGDH、PGD、PWCL和PWCH。三类寄存器的具体格式如图3.5所示。
-
-图 3.5: LoongArch指令系统TLB相关控制寄存器
+
++图 3.5: LoongArch指令系统TLB相关控制寄存器 +
上述寄存器中,第二类专用于TLB重填异常处理场景(CSR.TLBRERA的IsTLBR域值等于1)的控制寄存器,其设计目的是确保在非TLB重填异常处理程序执行过程中嵌套发生TLB重填异常处理后,原有异常处理程序的上下文不被破坏。例如,当发生TLB重填异常时,其异常处理返回地址将填入CSR.TLBRERA而非CSR.ERA,这样被嵌套的异常处理程序返回时所用的返回目标就不会被破坏。因硬件上只维护了这一套保存上下文专用的寄存器,所以需要确保在TLB重填异常处理过程中不再触发TLB重填异常,为此,处理器因TLB重填异常触发而陷入异常处理后,硬件会自动将虚实地址翻译模式调整为直接地址翻译模式,从而确保TLB重填异常处理程序第一条指令的取指和访存7一定不会触发TLB重填异常,与此同时,软件设计人员也要保证后续TLB重填异常处理返回前的所有指令的执行不会触发TLB重填异常。
在访问和控制TLB的控制状态寄存器中,ASID中的ASID域、TLBEHI中的VPPN域、TLBELO0和TLBELO1中的所有域、TLBIDX中的PS和E域所构成的集合对应了一个TLB表项中的内容(除了TLB表项中的G位域),ASID中的ASID域、TLBREHI中的VPPN和PS域、TLBRELO0和TLBRELO1中的所有域所构成的集合也对应了一个TLB表项中的内容(除了G位域和E位域)。这两套控制状态寄存器都用来完成TLB表项的读写操作,前一套用于非TLB重填异常处理场景,而后一套仅用于TLB重填异常处理场景。写TLB时把上述寄存器中各个域存放的值写到TLB某一表项(将TLBELO0和TLBELO1的G位域相与或者将TLBRELO0和TLBRELO1的G位域相与后写入TLB表项的G位域),读TLB时将TLB表项读到并写入上述寄存器中的对应域(将TLB表项的G位域的值同时填入TLBELO0和TLBELO1的G位域,或者同时填入TLBRELO0和TLBRELO1的G位域)。
@@ -682,20 +795,72 @@ div.csl-indent {Linux操作系统通常采用多级页表结构。对于64位的LoongArch处理器,如果其有效虚地址位宽为48位,那么当Linux操作系统采用16KB页大小时,其页表为三级结构,如图3.6所示。33位的虚双页号(VPPN)分为三个部分:最高11位作为一级页表(页目录表PGD)索引,一级页表中每一项保存一个二级页表(页目录表PMD)的起始地址;中间11位作为二级页表索引,二级页表中每一项保存一个三级页表(末级页表PTE)的起始地址;最低11位作为三级页表索引。每个三级页表包含2048个页表项,每个页表项管理一个物理页,大小为8字节,包括RPLV、NX、NR、PPN、W、P、G、MAT、PLV、D、V的信息。“P”和“W”两个域分别代表物理页是否存在,以及该页是否可写。这些信息虽然不填入TLB表项中,但用于页表遍历的处理过程。每个进程的PGD表基地址放在进程上下文中,内核进程进行切换时把PGD表的基地址写到CSR.PGDH的Base域中,用户进程进行切换时把PGD表的基地址写到CSR.PGDL的Base域中。
-
-图 3.6: Linux/LoongArch三级页表结构
+
++图 3.6: Linux/LoongArch三级页表结构 +
当TLB重填异常发生后,其异常处理程序的主要处理流程是根据CSR.TLBRBADV中VAddr域记录的虚地址信息以及从CSR.PGD中得到的页目录表PGD的基址信息,遍历发生TLB重填异常的进程的多级页表,从内存中取回页表项信息填入CSR.TLBRELO0和CSR.TLBRELO1的相应域中,最终用TLBFILL指令将页表项填入TLB。前面在讲述TLBFILL指令写操作过程时,提到此时写入TLB的信息除了来自CSR.TLBRELO0和CSR.TLBRELO1的各个域之外,还有来自CSR.ASID中ASID域和CSR.TLBREHI中VPPN域的信息。在TLB重填异常从发生到进行处理的过程中,软硬件都没有修改CSR.ASID中的ASID域,所以在执行TLBFILL指令时,CSR.ASID中的ASID域记录的就是发生TLB重填异常的进程对应的ASID。至于CSR.TLBREHI中的VPPN域,在TLB重填异常发生并进入异常入口时,已经被硬件填入了触发该异常的虚地址中的虚双页号信息。
整个TLB重填异常处理过程中,遍历多级页表是一个较为复杂的操作,需要数十条普通访存、运算指令才能完成,而且如果遍历的页表级数增加,则需要更多的指令。LoongArch指令系统中定义了LDDIR和LDPTE指令以及与之配套的CSR.PWCL和CSR.PWCH来加速TLB重填异常处理中的页表遍历。LDDIR和LDPTE指令的功能简述如表3.3所示。
-指令 | 描述 |
LDDIR rd, rj, level | 将rj寄存器中的值作为当前页目录表的基地址,同时根据CSR.TLBRBADV中VAddr域存放的TLB缺失地址以及PWCL、PWCH寄存器中定义的页目录表level索引的起始位置和位宽信息计算出当前目录页表的偏移量,两者相加作为访存地址,从内存中读取待访问页目录表/页表的基址,写入rd寄存器中。 |
LDPTE rj, seq | 将rj寄存器中的值作为末级页表的基地址,同时根据CSR.TLBRBADV中VAddr域存放的TLB缺失地址以及PWCL、PWCH寄存器中定义的末级页表索引的起始位置和位宽信息计算出末级页表的偏移量,两者相加作为访存地址,从内存中读取偶数号(seq=0)或奇数号(seq=1)页表项的内容,将其写入到TLBRELO0或TLBRELO1寄存器中。 |
CSR.PWCL和CSR.PWCH用来配置LDDIR和LDPTE指令所遍历页表的规格参数信息,其中CSR.PWCL中定义了每个页表项的宽度(PTEwidth域)以及末级页表索引的起始位置和位宽(PTbase和PTwidth域)、页目录表1索引的起始位置和位宽(Dir1_base和Dir1_width域)、页目录表2索引的起始位置和位宽(Dir2_base和Dir2_width域),CSR.PWCH中定义了页目录表3索引的起始位置和位宽(Dir3_base和Dir3_width域)、页目录表4索引的起始位置和位宽(Dir4_base和Dir4_width域)。在Linux/LoongArch64中,当进行三级页表的遍历时,通常用Dir1_base和Dir1_width域来配置页目录表PMD索引的起始位置和位宽,用Dir3_base和Dir3_width域来配置页目录表PGD索引的起始位置和位宽,Dir2_base和Dir2_width域、Dir4_base和Dir4_width域空闲不用。
使用上述指令,TLB重填异常处理程序如下。可见,遍历一个三级页表的处理过程只需要执行9条指令,且每增加一级页表只需增加一条LDDIR指令即可。
csrwr $t0, CSR_TLBRSAVE
@@ -758,7 +923,7 @@ else DO_FAULT(1);
+
5. 请用C语言伪代码形式描述一台64位LoongArch机器上的TLB进行访存虚实地址转换的过程(包含TLB地址翻译相关异常的判定过程)。(提示:①可以将TLB的每一项定义为一个结构体,将整个TLB视作一个结构体数组;②无须直接体现过程中电路的并发执行特性,只需要确保最终逻辑状态一致即可。)
片上Cache结构是通用多核处理器设计的重要内容。片上Cache的种类主要有:私有Cache、片上共享Cache、片间共享Cache。图11.1a是私有Cache结构示意图,图11.1b是片上共享Cache结构示意图(由于一级Cache的访问速度对性能影响大,通用多核处理器的一级Cache几乎都是私有的)。私有Cache结构具有较快的访问速度,但是具有较高的失效率。共享Cache结构的访问速度稍慢,但具有失效率低的优点。多处理器芯片间共享Cache结构的访问速度慢,且失效率高,因此并不常用。
-
-图 11.1: Cache结构示意图
+
++图 11.1: Cache结构示意图 +
目前,主流多核处理器的典型Cache 结构是:片内共享最后一级Cache(Last Level Cache,简称LLC),片间共享内存。表11.1列出了典型商用多核处理器的Cache结构参数。处理器核的一级Cache和二级Cache私有,三级Cache(LLC)共享。有些处理器甚至有片外的四级Cache,例如Intel i7处理器。
-⠀ | IBM Power8 | Intel Haswell | Oracle SPARC T5 | 龙芯3A5000 |
每芯片核数 | 12 | 4 | 16 | 4 |
每核线程数 | 8 | 2 | 8 | 1 |
每核一级指令Cache | 32KB | 32KB | 16KB | 64KB |
每核一级数据Cache | 64KB | 32KB | 16KB | 64KB |
每核二级Cache | 512KB | 256KB | 128 KB | 256KB |
片上共享LLC | 96MB | 8MB | 8MB | 16MB |
在共享LLC结构中,主要有UCA(Uniform Cache Access)和NUCA(Non-Uniform Cache Access)两种。图11.2为共享LLC结构示意图(假设二级Cache为LLC)。
-
-图 11.2: 共享LLC结构示意图
+
++图 11.2: 共享LLC结构示意图 +
UCA是一种集中式共享结构,多个处理器核通过总线或者交叉开关连接LLC,所有处理器核对LLC的访问延迟相同。这种集中式的共享LLC,很容易随着处理器核数目的增加成为瓶颈。另外,UCA结构由于使用总线或者交叉开关互连,可扩展性受限。因此,通常在处理器核数较少的多核处理器中采用UCA结构,例如四核龙芯3号处理器。
NUCA是一种分布式共享结构,每个处理器核拥有本地的LLC,并通过片上互连访问其他处理器核的LLC。在NUCA结构中,处理器核可以访问所有的LLC,但是不同位置的LLC具有不同的访问延迟。当工作集较小时,处理器核的本地Cache足够容纳工作集,处理器核只使用本地Cache;当工作集较大时,本地Cache中放不下的数据可以放到远地Cache中。NUCA结构需要高效Cache查找和替换算法,使得在使用远地Cache时不影响性能。NUCA结构中通常采用可扩展的片上互连(如Mesh片上网络等),采用基于目录的Cache一致性协议,具有良好的可扩展性,可以有效支持较多数目的处理器核。因此,在具有较多核数的多核/众核处理器中通常采用NUCA结构,如SPARC M7和龙芯3C5000等。
@@ -550,11 +607,61 @@ div.csl-indent {本节简要介绍常见的存储一致性模型。存储一致性模型最初是针对共享存储的多处理器设计提出来的,同样也可以适用于多核处理器设计。本节在介绍存储一致性模型时,处理器(处理机)和处理器核在概念是可以互用的。
下面举一个存储一致性问题的例子。如表11.2所示,寄存器R1为进程P2的内部寄存器,R2和R3为P3的内部寄存器,初始值均为0;变量a,b为P1、P2和P3的共享变量,初始值均为0。
-P1 | P2 | P3 |
L11: STORE a, 1; | L21: LOAD R1, a; | L31: LOAD R2, b; |
在表11.2所示的程序中,如果仅要求P1、P2及P3根据指令在程序中出现的次序来执行指令,那么这个程序的访存事件可能按如下次序发生:
2.Cache 状态
Cache一致性协议的实现方式为:在Cache中每一个Cache行设置一致性状态来记录该Cache行的读写状态,确保Cache行不会被多个处理器核同时修改。Cache行的一致性状态的实现有多种具体形式,如最简单的三状态ESI,较为常见的MESI及其变种MOESI等。
ESI 是指Cache 行的三种一致性状态:E(Exclusive,独占),S(Shared,共享),I (Invalid,无效)。Invalid状态表示当前Cache行是无效的,对其进行任何读写操作都会引发缓存缺失(Cache Miss)。Shared状态表明当前Cache行可能被多个处理器核共享,只能读取,不能写入,对其写入也会引发缓存缺失。Exclusive状态表明对应Cache行被当前处理器核独占,该处理器核可以任意读写这个Cache行,而其他处理器核如果想读写这个Cache行需要请求占有这个Cache行的处理器核释放该Cache行。图11.3给出了三个状态之间的转换关系。
-
-图 11.3: 三状态Cache一致性协议状态转换图
+
++图 11.3: 三状态Cache一致性协议状态转换图 +
MESI 在ESI 的基础上增加了M(Modified,修改)状态。其中Shared状态和Invalid状态和ESI的完全一样,而Exclusive 状态表示当前Cache块虽然被当前处理器核独占,但是还没有被修改,与内存中的数据保持一致,如果处理器核想将其替换出去,并不需要将该Cache行写回内存。Modified状态表示当前Cache行被当前处理器核独占并且已经被修改过了,如果处理器核想替换该Cache行,需要将该Cache行写回内存。与ESI协议相比,增加一个Modified状态的优点是减少了Cache到内存的数据传输次数,Cache只需要将Modified状态的Cache行写回内存。
下面通过一个写无效的位向量目录协议例子简单说明Cache一致性协议的工作原理。通常,一个Cache一致性协议应包括以下三方面的内容:Cache行状态、存储行状态以及为保持Cache一致性的状态转化规则。
@@ -597,22 +706,28 @@ div.csl-indent {当处理器核Pi发出一存数操作“STORE x”时,根据x 在Cache和存储器中的不同状态采取如下不同的操作:若x在Pi的Cache中处于独占状态,则存数操作“STORE x”在Cache 命中。若x在Pi的Cache中处于共享状态,那么这个处理器核向存储器发出一个写数请求write(x),存储器在收到这个write(x)后查找与单元x 相对应的目录项,如果目录项的内容显示出x所在的存储行处于CLEAN 状态(改写位为“0”),并没有被其他处理器核所共享(位向量中所有位都为“0”),那么存储器向发出请求的处理器核Pi发出写数应答wtack(x)表示允许Pi独占x所在行,把目录项中的改写位置为“1”并把位向量的第i位置为“1”;如果目录项的内容显示出x所在的存储行处于CLEAN 状态(改写位为“0”),并且在其他处理器核中有共享备份(位向量中有些位为“1”),那么存储器根据位向量的内容向所有持有x的共享备份的处理器核发出一个使无效信号invld(x),持有x的有效备份的处理器核在收到invld(x)后把x在Cache的备份从共享状态(SHD)改为无效状态(INV),并向存储器发出使无效应答invack(x),存储器收到所有invack(x)后向发出请求的处理器核Pi发出写数应答wtack(x),把目录项中的改写位置为“1”并把位向量的第i位置为“1”,其他位清“0”。若x在Pi的Cache中处于无效状态,那么这个处理器核向存储器发出一个写数请求write(x),存储器在收到这个write(x)后查找与单元x相对应的目录项,如果目录项的内容显示出x所在的存储行处于CLEAN 状态(改写位为“0”),并没有被其他处理器核所共享(位向量中所有位都为“0”),那么存储器向发出请求的处理器核Pi发出写数应答wtack(x)提供x所在行的一个有效备份,把目录项中的改写位置为“1”,并把位向量的第i位置为“1”;如果目录项的内容显示出x所在的存储行处于CLEAN 状态(改写位为“0”),并且在其他处理器核中有共享备份(位向量中有些位为“1”),那么存储器根据位向量的内容向所有持有x的共享备份的处理器核发出一个使无效信号invld(x),持有x的有效备份的处理器核在收到invld(x)后,把x在Cache 的备份从共享状态(SHD)改为无效状态(INV),并向存储器发出使无效应答invack(x),存储器收到所有invack(x)后向发出请求的处理器核Pi发出写数应答wtack(x)提供x所在行的一个有效备份,把目录项中的改写位置为“1”并把位向量的第i 位置为“1”,其他位清“0”;如果目录项的内容显示出x所在的存储行已被某个处理器核Pk改写(改写位为“1”,位向量第k 位为“1”),那么存储器向Pk发出一个使无效并写回请求invwb(x),Pk在收到invwb(x)后把x在Cache的备份从独占状态EXC改为无效状态INV,并向存储器发出使无效并写回应答invwback(x)提供x所在行的有效备份,存储器收到来自Pk 的invwback(x)后向发出请求的处理器核Pi发出写数应答wtack(x)提供x所在行的一个有效备份,把目录项中的改写位置为“1”,并把位向量的第i位置为“1”,其他位清“0”。如果x不在Pi的Cache中,那么Pi先从Cache中替换掉一行再向存储器发出一个写数请求write(x)。
如果某处理器核要替换一Cache行且被替换行处在EXC状态,那么这个处理器核需要向存储器发出一个替换请求rep(x)把被替换掉的行写回存储器。
假设单元x 初始时在存储器中处于CLEAN状态(改写位为“0”),并被处理器核Pj和Pk所共享(在Pj和Pk的Cache中处于SHD状态),如图11.4a所示。接着x被多个处理器核按如下次序访问:处理器核Pi发出存数操作“STORE x”,处理器核Pk发出存数操作“STORE x”,处理器核Pi发出取数操作“LOAD x”,处理器 Pj发出取数操作“LOAD x”。图11.4b~f显示出上述访问序列引起的一系列消息传递,以及x在Cache及在存储器中的状态的转化过程。
-
-图 11.4: 基于目录的写无效Cache一致性协议
+
++图 11.4: 基于目录的写无效Cache一致性协议 +
多核处理器通过片上互连将处理器核、Cache、内存控制器、IO 接口等模块连接起来。图11.5为一个NUCA结构的多核处理器的片上互连示意图。常见的片上互连结构包括片上总线、交叉开关和片上网络。图11.6为三种结构的对比示意图。其中共享总线结构和交叉开关结构因可伸缩性差的原因,主要用于小规模的多核处理器;片上网络(Network-on-Chip,简称NOC)具有可伸缩性好的优势,适合于核数较多的多核/众核处理器。
-
-图 11.5: NUCA架构多核处理器的片上互连
+
++图 11.5: NUCA架构多核处理器的片上互连 +
-图 11.6: 片上互连结构分类
+
++图 11.6: 片上互连结构分类 +
1.片上总线
传统的计算机系统的总线通常由一组信号线把多功能模块连接在一起。通过信号线上的信号表示信息,通过约定不同信号的先后次序约定操作如何实现。根据传输信息的种类不同,可以划分为数据总线、地址总线和控制总线,分别用来传输数据、数据地址和控制信号。标准化的总线可以方便各部件间互连,因此出现了许多总线标准,例如ISA、PCI、USB总线标准等。
@@ -623,14 +738,18 @@ div.csl-indent {交叉开关的优点是高带宽,多对输入与输出端口间可以并行通信,且总带宽随所连接节点数的增加而增加。但缺点是随着连接节点数的增加,交叉开关需要的交叉点数目增加较快,物理实现代价较高,复杂度为O(M*N),因此可伸缩性有限,也不适合连接节点数多的情况。例如,对于一个有M个输入端口和N个输出端口的交叉开关,要增加成M+1输入端口和N+1个输出端口的交叉开关,则需要增加M+N+1个交叉点。四核龙芯3号处理器的设计即采用交叉开关来互连处理器核和共享二级Cache体。
3.片上网络
针对传统互连结构的局限,C. Seitz和W. Dally在21世纪初首先提出了片上网络的概念。图11.7中有6个处理器核节点连接到网络中(P0~P5),当节点P2与P5进行数据通信时,它首先发送一个带有数据包的消息到网络中,然后网络将这个消息传输给P5。片上网络借鉴了分布式网络的TCP/IP协议传输数据的方式,将数据封装成数据包,通过路由器之间的分组交换和对应的存储-转发机制来实现处理器核间的通信。在片上网络中,片上多核处理器被抽象成节点、互连网络、网络接口(Network Interface)等元素。片上网络研究内容主要包括:拓扑结构、路由算法、流量控制(Flow Control)、服务质量等。
-
-图 11.7: 片上网络示意图
+
++图 11.7: 片上网络示意图 +
1)拓扑结构。片上网络是由节点和传输信道的集合构成的。片上网络的拓扑是指网络中节点和信道的排列方式。环(Ring)、网格(Mesh)拓扑结构为最常见的两种。如图11.8所示,Mesh拓扑结构中包含16个节点,编号为0到15,每个节点与4条边相连,但因为图中所示的边是双向的,每一条边可以看作两条方向相反的有向边,因此图中每个节点实际上是与8条信道线路相连。IBM CELL处理器和Intel SandyBridge处理器采用环连接,Tilera公司的Tile64处理器采用Mesh互连。
-
-图 11.8: 拓扑结构示意图
+
++图 11.8: 拓扑结构示意图 +
2)路由算法。片上网络所采用的路由方法决定了数据包从源节点到目的节点的传输路径。路径是传输信道的集合,即\(P = {c_1,c_2,…c_k}\), 其中当前信道\(c_i\)的输出节点与下一跳信道\(c_{i+1}\)的输入节点相同。在某些片上网络拓扑结构中(如环),从某个源节点出发到目的节点的路径只有唯一的一条;对于某些片上网络拓扑结构来说(如Mesh),可能有多条路径。
路径的选择可以遵循很多原则,针对如Mesh这样的网络拓扑结构,最常见的最短路径选择有两种:
@@ -639,43 +758,60 @@ div.csl-indent {全局自适应路由(Adaptive Routing)。这是为了解决局部负载不均衡的情况而产生的路由方法,简单来说就是在每个节点有多种方向选择时,优先选择负载较轻的那一个节点方向作为路径。
3)路由器结构。路由器由寄存器、交叉开关、功能单元和控制逻辑组成,这些部件共同实现了路由计算和流控制为了存储和转发流控单元(flit)到它们的目的地节点所需的控制功能。这里主要介绍经典的路由器结构。图11.9所示为一个适用于Mesh结构的路由器结构。节点的每一个输入端口都有一个独立的缓冲区(Buffer),在数据包可以获得下一跳资源离开之前,缓冲区将它们存储下来。交叉开关连接输入端的缓冲区和输出端口,数据包通过交叉开关控制传输到它指定的输出端口。分配器包括路由计算、虚通道分配和交叉开关分配三种功能,路由计算用来计算head flit的下一跳输出方向,虚通道分配用来分配flit在缓冲队列的位置,交叉开关分配用来仲裁竞争的flit中哪个可以获得资源传输到输出端口。
-
-图 11.9: 路由器结构图
+
++图 11.9: 路由器结构图 +
4)流量控制。流量控制用来组织每个处理器核节点中有限的共享资源,片上网络的主要资源就是信道(Channel)和缓冲区(Buffer)。信道主要用来传输节点之间的数据包。缓冲区是节点上的存储装置,比如寄存器、内存等,它用来临时存储经过节点的数据包。当网络拥塞时,数据包需要临时存在缓冲区中等待传输。为了充分实现拓扑结构和路由方法的性能,当有空闲的信道可以使用时,流量控制必须尽量避免资源的冲突发生。好的流量控制策略要求它保持公平性和无死锁,不公平的流量控制极端情况会导致某些数据包陷入无限等待状态,死锁是当一些数据包互相等待彼此释放资源而造成的无限阻塞的情况。片上网络为了可以有效执行,一定要是无死锁的。
-下面以经典的基于信用的流量控制为例介绍片上网络中流量控制方法。如图11.10所示,每一个处理器核节点的输入端口有自己的缓冲区队列,分别用来存取来自对应的上一跳节点的数据,比如i+1号节点最左侧的Buffer用来存储来自i号节点的数据包。同时,每个节点上对应其相邻的节点都有一个计数器,分别是S[0]~S[3],用来记录相邻节点内缓冲区Buffer的使用情况。
-举例来说,对于处理器核节点i的每一个计数器的初始状态S[0-3]都设为0,当它向相邻节点如i+1号节点发送flit时,首先判断S[0]的值是否已达到Buffer的最大值,如果没有,则将S[0]的值加1,然后将flit发送过去,如果S[0]已经达到最大值,则数据会被扣留在Buffer中直到右侧节点有足够的空间收留来自它的数据。同时,对于i+1号节点,每当它左侧的Buffer送走一个flit时,它就向其左侧的节点发送一个Credit信号,通知左侧节点,此Buffer已多出一个空余位置,当左侧节点收到此Credit信号后,则会更新对应的S[0]减1。整个流程如图11.10所示。
-
-## 多核处理器的同步机制
下面以经典的基于信用的流量控制为例介绍片上网络中流量控制方法。如图11.10所示,每一个处理器核节点的输入端口有自己的缓冲区队列,分别用来存取来自对应的上一跳节点的数据,比如i+1号节点最左侧的Buffer用来存储来自i号节点的数据包。同时,每个节点上对应其相邻的节点都有一个计数器,分别是S[0]~S[3],用来记录相邻节点内缓冲区Buffer的使用情况。
+举例来说,对于处理器核节点i的每一个计数器的初始状态S[0-3]都设为0,当它向相邻节点如i+1号节点发送flit时,首先判断S[0]的值是否已达到Buffer的最大值,如果没有,则将S[0]的值加1,然后将flit发送过去,如果S[0]已经达到最大值,则数据会被扣留在Buffer中直到右侧节点有足够的空间收留来自它的数据。同时,对于i+1号节点,每当它左侧的Buffer送走一个flit时,它就向其左侧的节点发送一个Credit信号,通知左侧节点,此Buffer已多出一个空余位置,当左侧节点收到此Credit信号后,则会更新对应的S[0]减1。整个流程如图11.10所示。
+
++图 11.10: 基于信用的流量控制 +
+在介绍多核处理器的同步机制之前,先来看一个同步问题的例子。有两个处理器核P0和P1分别对同一共享地址的变量A进行加1的操作。于是,处理器P0先读取A的值,然后加1,最后将A写回内存。同样,处理器核P1也进行一样的操作。然而,如图11.11所示,实际的运算过程却有可能产生两种不一样的结果,注意整个运算过程是完全符合Cache一致性协议规定的。所以A的值可能增加了1,如图11.11a所示;也可能增加了2,如图11.11b所示。然而,这样的结果对于软件设计人员来说是完全无法接受的。因此,需要同步机制来协调多个处理器核对共享变量的访问。
-
-图 11.11: 一个并行程序产生两种不同结果
+
++图 11.11: 一个并行程序产生两种不同结果 +
为了解决同步问题,需要采用同步机制。常见的同步机制包括锁操作、栅障操作和事务内存。锁操作和事务内存主要用于保护临界区;栅障操作用于实现全局同步。锁操作和栅障操作属于传统同步方法,广泛用于并行系统中,事务内存则是适应多核处理器设计需求的一种新同步机制。同步机制一般建立在用户级软件例程(Routine)上,而这些软件例程主要基于硬件提供的同步指令来实现。
1.原子操作
硬件设计人员在处理器中增加了一种特殊的机制,支持多个操作之间的原子性(Atomic,也就是不可分割性)。在硬件上实现满足不可分割性的原子操作有许多种方法,既可以在寄存器或者存储单元中增加专门的硬件维护机制,也可以在处理器的指令集中添加特定的原子指令。早期的处理器大多选择在存储单元中增加特殊的原子硬件维护机制,而现代处理器大多使用原子指令方式。原子指令的实现方式可以分为两种,其中一种是直接使用一条“读-改-写”(Read-Modify-Write,RMW)原子指令来完成,另一种是使用一组原子指令对LL/SC(Load-Linked/Store-Conditional)来完成指定的原子操作。
常见的“读-改-写”原子指令包括Test_and_Set、Compare_and_Swap、Fetch_and_Op等。Test_and_Set 指令取出内存中对应地址的值,同时对该内存地址赋予一个新的值。Compare_and_Swap指令取出内存中对应地址的值和另一个给定值A进行比较,如果相等,则将另一个给定值B写入这个内存地址,否则不进行写操作;指令应返回状态(例如X86的cmpxchg指令设置eflags的zf位)来指示是否进行了写操作。Fetch_and_Op指令在读取内存对应地址值的同时将该地址的值进行一定的运算再存回。根据运算操作(Op)的不同,Fetch_and_Op指令又有许多种不同的实现形式。例如,Fetch_and_Increment指令就是读取指定地址的值,同时将该值加1并写回内存。可以看出“读-改-写”原子指令和内存的交互过程至少有两次,一次读内存,另一次写内存,而两次交互过程之间往往还有一些比较、加减之类的运算操作(改)。
使用原子指令对LL/SC实现原子操作方式的过程如下:首先,LL指令将对应地址的内存数据读入寄存器,然后可以对该寄存器中的值进行任意的运算,最后使用SC指令尝试将运算后的数据存回内存对应的地址。当且仅当LL指令完成之后没有其他对该地址内存数据的修改操作,则SC指令执行成功并返回一个非零值,运算后的数据顺利写回内存,否则SC指令执行失败并返回值0,修改后的数据不会被写回内存,也不会产生任何对内存的改动。SC指令失败后一般需要重新执行上述过程,直到SC指令成功为止。SC指令的成功说明了LL/SC指令之间没有其他对同一地址的写入操作,也就保证了LL/SC指令之间的不可分割性。图11.12的例子采用LL/SC指令实现了寄存器R1的内容与R3对应的内存位置的内容的原子交换。
-
-图 11.12: 用LL/SC指令对实现原子交换操作
+
++图 11.12: 用LL/SC指令对实现原子交换操作 +
LL/SC原子指令对的优点在于设计简单,每条指令只需和内存交互一次,且在LL指令和SC指令之间可以加入任意的运算指令,可以灵活地实现类似于“读-改-写”的复杂原子操作。其缺点在于密集共享时,SC不容易成功,一种优化措施是LL访问时把相应Cache行置为EXC状态,而不是SHD状态,这样可以提高SC成功的概率。相对于Test_and_Set指令和Fetch_and_Op指令等实现复杂的单条原子指令,LL/SC指令对成为目前最常见的原子指令,被多种现代RISC指令系统所采用,如MIPS、IBM Power、DEC Alpha和LoongArch等等。
2.锁的软件实现方法
锁(Lock)是并行程序中常用的对多个线程共享的临界区(Critical Section)进行保护的同步操作。自旋锁(Spin Lock)是锁操作的一种最基本的实现形式。Test_and_Set自旋锁是最简单的自旋锁,通过使用Test_and_Set原子指令来完成锁的获取、等待和查询。Test_and_Set锁的基本步骤如图11.13所示,假设1表示锁被占用,0表示锁空闲。处理器使用Test_and_Set原子指令读取锁变量的值,同时将锁变量的值改为1。如果读取到锁的值为0,说明锁空闲,该处理器成功获得锁。由于Test_and_Set指令已经将锁的值同时改为了1,所以其他处理器不可能同时获得这把锁。如果锁的值为1,说明已经有其他处理器占用了这把锁,则该处理器循环执行Test_and_Set指令自旋等待,直到成功获得锁。由于当时锁的值已经是1了,Test_and_Set指令再次将锁的值设为1,实际上锁的值并没有发生变化,所以不会影响到锁操作的正确性。当获得锁的处理器打算释放锁时,只需要简单地执行一条普通的store指令,将锁的值设置为0即可。由于一次只能有一个处理器核获得锁,所以不用担心多个处理器核同时释放锁而引发访存冲突,也就不需要使用原子指令来释放锁了。
-
-图 11.13: Test-and-Set自旋锁
+
++图 11.13: Test-and-Set自旋锁 +
Test_and_Set自旋锁最主要的一个缺点就是对锁变量的访存冲突。当一个处理器核获得锁以后,其他等待的处理器核会不断循环执行Test_and_Set指令访问锁变量,试图获取锁权限,从而在片上互连上产生大量的访存通信。一种简单的优化方法就是在Test_and_Set指令之间加入一定的延迟,减少等待阶段Test_and_Set原子指令自旋执行的次数以减轻访存的压力。此外,研究人员还提出了排队锁(Ticket Lock)、基于数组的队列锁(Array-Based Queuing Lock)、基于链表的队列锁(List-Based Queuing Lock)等优化机制。
3.栅障软件实现方法
栅障(Barrier)是并行程序中常用的同步操作。栅障要求处理器核等待,一直到所有处理器核都到达栅障后,才能释放所有处理器核继续执行。栅障有多种实现方式,下面主要介绍比较简单的集中式栅障。集中式栅障就是在共享存储中设置一个共享的栅障变量。每当一个处理器核到达栅障以后,就使用原子指令修改栅障值表示自己已经到达(如将栅障的值加1),然后对该栅障值进行自旋等待,如图11.14的伪代码所示。当栅障的值表明所有处理器核都已经到达(即栅障的值等于预计到达的总的处理器核的数量)时,栅障操作顺利完成,所有自旋等待的处理器核就可以继续往下执行了。集中式栅障的实现简单、灵活,可以支持各种类型的栅障,包括全局栅障和部分栅障,适用于可变处理器核数量的栅障操作。
在集中式栅障中,每一个到达的处理器核都需要对同一个共享的栅障值进行一次修改以通告该处理器核到达栅障,已到达栅障的处理器核会不断访问栅障值以判断栅障是否完成。由于Cache一致性协议的作用,这个过程会在片上互连上产生许多无用的访存通信,并且随着处理核数的增加,栅障的时间和无用的访存数量都会快速增长,所以集中式栅障的可扩展性不好。为了减少上述查询和无效的访存开销,集中式栅障也可以采用类似于Test_and_Set锁的方式,在查询操作之中增加一些延迟。加入延迟虽然可以减少一些网络带宽的浪费,但是也可能降低栅障的性能。针对集中式栅障的弱点,研究人员提出了软件合并树栅障等优化方法。
-
-图 11.14: 集中式栅障伪代码
+
++图 11.14: 集中式栅障伪代码 +
4.事务内存
1993年,Herlihy和Moss以事务概念为基础,针对多核处理器中并行编程的同步效率问题提出了事务内存的概念。在事务内存中,访问共享变量的代码区域声明为一个事务(Transaction)。事务具有原子性(Atomicity),即事务中的所有指令要么执行要么不执行;一致性(Consistency),即任何时刻内存处于一致的状态;隔离性(Isolation),即事务不能看见其他未提交事务涉及的内部对象状态。事务执行并原子地提交所有结果到内存(如果事务成功),或中止并取消所有的结果(如果事务失败)。
@@ -683,62 +819,78 @@ div.csl-indent {事务内存实现方式主要有软件事务内存和硬件事务内存两种。软件事务内存在通过软件实现,不需要底层硬件提供特殊的支持,主要以库函数或者编程语言形式实现。例如,RSTM、DSTM、Transactional Locking等以库函数实现,线程访问共享对象时通过对应的库函数来更新事务执行的状态、检测冲突和处理等;HSTM语言中扩展了事务原语;AtomCaml在ObjectCaml语言中增加了对事务内存同步模型的支持等。硬件事务内存主要对多核处理器的Cache结构进行改造,主要包括:增加特定指令来标示事务的起止位置,使用额外的事务Cache来跟踪事务中的所有读操作和写操作;扩展Cache一致性协议来检测数据冲突。软件事务内存实现灵活,更容易集成到现有系统中,但性能开销大;硬件事务内存需要修改硬件,但是性能开销小,程序整体执行性能高。Intel Haswell处理器和IBM Power8处理器中实现了对硬件事务内存的支持。下面来看一个具体的实现例子。
Intel TSX(Transactional Synchronization Extensions)是Intel公司针对事务内存的扩展实现,提出了一个针对事务内存的指令集扩展,主要包括3条新指令:XBEGIN、XEND和XABORT。XBEGIN指令启动一个事务,并提供了如果事务不能成功执行的回退地址信息;XEND指令表示事务的结束;XABORT指令立刻触发一个中止,类似于事务提交不成功。硬件实现以Cache行为单位,跟踪事务的读集(Read-Set)和写集(Write-Set)。如果事务读集中的一个Cache行被另一个线程写入,或者事务的写集中的一个Cache行被另一个线程读取或写入,则事务就遇到冲突(Conflict),通常导致事务中止。Intel Haswell处理器中实现了Intel TSX。
龙芯3A5000于2020年研制成功,是龙芯中科技术股份有限公司研发的首款支持龙芯自主指令集(LoongArch)的通用多核处理器,主要面向桌面计算机和服务器应用。龙芯3A5000片内集成4个64位GS464V高性能处理器核、16MB的分体共享三级Cache、2个DDR4内存控制器(支持DDR4-3200)、2个16位HT(HyperTransport)控制器、2个I2C、1个UART、1个SPI、16路GPIO接口等。龙芯3A5000中多个GS464V核及共享三级Cache模块,通过AXI互连网络形成一个分布式共享片上末级Cache的多核结构。采用基于目录的Cache一致性协议来维护Cache一致性。另外,龙芯3A5000还支持多片扩展,将多个芯片的HT总线直接互连就形成更大规模的共享存储系统(最多可支持16片互连)。
GS464V是支持Loongarch指令集的四发射64位高性能处理器核,具有256位向量部件。GS464V的结构如图11.15所示,主要特点如下:四发射超标量结构,具有四个定点、两个向量、两个访存部件;支持寄存器重命名、动态调度、转移预测等乱序执行技术;每个向量部件宽度为256位,可支持8个双32位浮点乘加运算或4个64位浮点运算;一级指令Cache和数据Cache大小各为64KB,4路组相联;牺牲者Cache(Victim Cache)作为私有二级Cache,大小为256KB,16路组相连;支持非阻塞(Non-blocking)访问及装入猜测(Load Speculation)等访存优化技术;支持标准的JTAG调试接口,方便软硬件调试。
-
-图 11.15: GS464V处理器核结构
+
++图 11.15: GS464V处理器核结构 +
龙芯3A5000芯片整体架构基于多级互连实现,结构如图11.16所示(图11.17为芯片版图)。第一级互连采用5x5的交叉开关,用于连接四个GS464V核(作为主设备)、四个共享Cache模块(作为从设备)、以及一个IO端口连接IO-RING。IO端口使用一个Master和一个Slave。第二级互连采用5x3的交叉开关,连接4个共享Cache模块(作为主设备),两个DDR3/4内存控制器、以及一个IO端口连接IO-RING。IO-RING连接包括4个HT控制器,MISC模块,SE模块与两级交叉开关。两个HT控制器(lo/hi)共用16位HT总线,作为两个8位的HT总线使用,也可以由lo独占16位HT总线。HT控制器内集成一个DMA控制器,DMA控制器负责IO的DMA控制并负责片间一致性的维护。上述互连结构都采用读写分离的数据通道,数据通道宽度为128bit,与处理器核同频,用以提供高速的片上数据传输。此外,一级交叉开关连接4个处理器核与Scache的读数据通道为256位,以提高片内处理器核访问Scache的读带宽。
龙芯3A5000主频可达2.5GHz,峰值浮点运算能力达到160GFLOPS。
-
-图 11.16: 龙芯3A5000芯片结构
+
++图 11.16: 龙芯3A5000芯片结构 +
-图 11.17: 龙芯3A5000的版图
+
++图 11.17: 龙芯3A5000的版图 +
Intel SandyBridge架构于2011年推出,是Intel面向32nm工艺的新架构,它是Core处理器架构的第二代架构。根据面向移动、桌面还是服务器应用,有支持2~8核的不同处理器产品。
SandyBridge处理器主要包括五个组成部分:处理器核、环连接(Ring Interconnect)、共享的三级Cache、系统代理(System Agent)和图形核心(GPU)。图11.18为Sandybridge处理器的结构示意图。它的处理器核心采用乱序执行技术,支持双线程,支持AVX向量指令集扩展。系统代理包括内存控制器、功耗控制单元(Power Control Unit)、PCIE接口、显示引擎和DMI等。存储层次包括每个核私有的一级和二级Cache、多核共享的LLC(三级Cache)。LLC分体实现,在处理器核和图形核心、系统代理之间共享。
SandyBridge采用环连接来互连处理器核、图形核心、LLC和系统代理。环连接由请求(Request)、响应(Acknowledge)、侦听(Snoop)、数据(Data)四条独立的环组成。这四条环采用一个分布式的通信协议维护数据一致性和序(Ordering),实现了基于侦听的Cache一致性协议。环连接采用完全流水线设计,以核心频率运行,随着连接的节点数目增加,带宽也随之增加,在处理器核总数不太大的情况下,有较好的伸缩性。另外,由于环连接传递的消息具有天然的序,使得Cache一致性协议的设计和验证比较简单。如图11.18所示,SandyBridge的环有6个接口,包括4个处理器核和三级Cache共享的接口,一个图形核心的接口和1个系统代理的接口。
4核SandyBridge处理器的主频达到3GHz,支持128位向量处理,峰值性能达到96GFLOPS,理论访存带宽达到25.6GB/s,采用Stream测试程序集实测的访存带宽为14GB/s~16GB/s。
-
-图 11.18: SandyBridge结构示意图
+
++图 11.18: SandyBridge结构示意图 +
Cell处理器由IBM、索尼和东芝联合研发,并在2005年国际固态电路会议(ISSCC)上首次公开,主要面向游戏、超级计算等领域。图11.19为Cell处理器的结构示意图。Cell采用异构多核架构,它由1个相对比较简单的支持同时双线程并行的双发射64位PowerPC内核(称为PPE)和8个SIMD型向量协处理器(称为SPE)构成。由一个高带宽的片上环状高速总线将PPE、SPE、RAM内存总线接口控制器(BIC)、FlexIO外部总线接口控制器连接起来。PPE主要负责控制并运行操作系统,SPE完成主要的计算任务。SPE的SIMD执行部件是128位宽的,从而可在一个时钟周期里完成4个32位的定点或浮点乘加运算。SPE里内置了256KB的SRAM作为局部存储器(Local Storage,简称LS),LS与内存间的通信必须通过DMA 进行。SPE配置了较大的寄存器堆(128个128位的寄存器)来尽量减少对内存的访问。由于SPE不采用自动调配数据的Cache机制,需要显式地将内存中的数据先搬到LS中供SPE计算,为了减少数据搬运,需要依赖高水平程序员或编译器的作用来获得高性能,编程较为困难。
Cell处理器可在4GHz频率下工作,峰值浮点运算速度为256GFLOPS,理论访存带宽为25.6GB/s。由于存在编程及推广困难等原因,目前Cell处理器已经停止研发。
-
-图 11.19: IBM Cell结构示意图
+
++图 11.19: IBM Cell结构示意图 +
GPU(Graphics Processing Unit)是进行快速图形处理的硬件单元,现代GPU包括数百个并行浮点运算单元,是典型的众核处理器架构。本节主要介绍NVIDIA公司的Fermi GPU体系结构。
第一个基于Fermi体系结构的GPU芯片有30亿个晶体管,支持512个CUDA核心,组织成16个流多处理器(Stream Multiprocessor,简称SM)。SM结构如图11.20、11.21所示。每个SM包含32个CUDA核心(Core)、16个load/store单元(LD/ST)、4个特殊处理单元(Special Function Unit,简称SFU)、64KB的片上高速存储。每个CUDA核心支持一个全流水的定点算术逻辑单元(ALU)和浮点单元(FPU)(如图11.22所示),每个时钟周期可以执行一条定点或者浮点指令。ALU支持所有指令的32位精度运算;FPU实现了IEEE 754-2008浮点标准,支持单精度和双精度浮点的融合乘加指令(Fused Multiply-Add, 简称FMA)。16个load/store单元可以每个时钟周期为16个线程计算源地址和目标地址,实现对这些地址数据的读写。SFU支持超越函数的指令,如sin、cos、平方根等。64KB片上高速存储是可配置的,可配成48KB的共享存储和16KB一级Cache或者16KB共享存储和48KB一级Cache。片上共享存储使得同一个线程块的线程之间能进行高效通信,可以减少片外通信以提高性能。
-
-图 11.20: 单个Fermi流多处理器结构图
+
++图 11.20: 单个Fermi流多处理器结构图 +
-图 11.21: Fermi流多处理器整体结构图
+
++图 11.21: Fermi流多处理器整体结构图 +
-图 11.22: CUDA核结构
+
++图 11.22: CUDA核结构 +
1.Fermi的线程调度
Fermi体系结构使用两层分布式线程调度器。块调度器将线程块(Thread Block)调度到SM上, SM以线程组Warp为单位调度执行,每个Warp包含32个并行线程,这些线程以单指令多线程(Single Instruction Multi Thread,简称SIMT)的方式执行。SIMT类似于SIMD,表示指令相同但处理的数据不同。每个SM有两个Warp调度器和两个指令分派单元,允许两个Warp被同时发射和并发执行。双Warp调度器(Dual Warp Scheduler)选择两个Warp,从每个Warp中发射一条指令到一个16个核构成的组、16个load/store单元,或者4个SFU单元。大多数指令是能够双发射的,例如两条定点指令、两条浮点指令,或者是定点、浮点、load、store、SPU指令的混合。双精度浮点指令不支持与其他指令的双发射。
@@ -749,30 +901,86 @@ div.csl-indent {3)L2 Cache。768KB统一的二级Cache在16个SM间共享,服务于所有到全局内存中的load/store操作。
4)全局存储。所有线程共享的片外存储。
Fermi体系结构采用CUDA编程环境,可以采用类C语言开发应用程序。NVIDIA将所有形式的并行都定义为CUDA线程,将这种最底层的并行作为编程原语,编译器和硬件可以在GPU上将上千个CUDA线程聚集起来并行执行。这些线程被组织成线程块,以32个为一组(Warp)来执行。Fermi体系结构可以看作GPU与CPU融合的架构,具有强大的浮点计算能力,除了用于图像处理外,也可作为加速器用于高性能计算领域。采用Fermi体系结构的GeForce GTX 480包含480核,主频700MHz,单精度浮点峰值性能为1.536TFLOPS,访存带宽为177.4GB/s。
-
-图 11.23: Fermi的存储层次图
+
++图 11.23: Fermi的存储层次图 +
Tile64是美国Tilera公司于2007年推出的64核处理器,主要面向网络和视频处理等领域。图11.24为Tile64处理器的结构图。Tile64具有64个Tile(瓦片),组成8*8的Mesh结构,每个Tile包含通用CPU核、Cache和路由器。Tile64的处理器核支持MIPS类VLIW指令集,采用三发射按序短流水线结构,支持两个定点功能部件和1个load/store访存部件。在互连结构方面,Tile64采用Mesh互连结构,通过路由器实现了5套低延迟的、不同用途的Mesh互连网络,提供了足够的通信带宽。在访存结构方面,每个Tile拥有私有一级Cache(16KB)和私有二级Cache(64KB),以及虚拟的三级Cache(所有Tile的二级Cache聚合)。Tile64采用邻居(Neighborhood)缓存机制实现片上分布式共享Cache,每个虚拟地址对应一个HomeTile,先访问该HomeTile的私有Cache,如果不命中则访问内存;数据只在它的Home Tile的私有Cache中缓存,由Home Tile负责维护数据一致性。Tile64支持4个DDR2内存控制器,2个10Gbit的以太网接口,2个PCIE接口及其他一些接口。Tile64的运行主频为1GHz,峰值性能为每秒192G个32位运算,理论访存带宽为25GB/s。
-
-图 11.24: Tile64处理器结构图
+
++图 11.24: Tile64处理器结构图 +
可以从以下几个维度对多核处理器结构进行分析:一是从处理器核及访存带宽的维度,包括核的数量、大核还是小核、同构核还是异构核、通用核还是专用核等,访存带宽与峰值计算能力之间的比例决定该多核处理器的通用性。二是从存储一致性模型的维度,存储一致性模型对多个处理器核发出的访存指令次序进行约定,包括顺序一致性模型、处理器一致性模型、弱一致性模型等。三是从Cache组织及一致性协议的维度,包括有几级Cache、Cache容量、私有还是共享Cache,Cache一致性协议是把一个处理器核新写的值传播到其他处理器核的一种机制。四是从片上互连结构的维度,即多个核处理器核间如何实现通信。五是多核之间的同步机制的维度,如互斥锁操作(lock)、路障操作(barrier)等。
P1 | P2 |
X=1; | Y=1; |
采用共享存储与消息传递编程模型编写的并行程序是在多处理器并行处理系统上运行的。先了解一下多处理器的结构特点,可以更好地理解并行编程模型。从结构的角度看,多处理器系统可分为共享存储系统和消息传递系统两类。在共享存储系统中,所有处理器共享主存储器,每个处理器都可以把信息存入主存储器,或从中取出信息,处理器之间的通信通过访问共享存储器来实现。而在消息传递系统中,每个处理器都有一个只有它自己才能访问的局部存储器,处理器之间的通信必须通过显式的消息传递来进行。消息传递和共享存储系统的原理结构如图10.1所示。从图中可以看出,在消息传递系统中,每个处理器的存储器是单独编址的;而在共享存储系统中,所有存储器统一编址。典型的共享存储多处理器结构包括对称多处理器机(Symmetric Multi-Processor,简称SMP)结构、高速缓存一致非均匀存储器访问(Cache Coherent Non Uniform Memory Access,简称CC-NUMA)结构。
-
-图 10.1: 消息传递(左)和共享存储系统(右)
+
++图 10.1: 消息传递(左)和共享存储系统(右) +
在消息传递编程模型中,程序员需要对计算任务和数据进行划分,并安排并行程序执行过程中进程间的所有通信。在共享存储编程模型中,由于程序的多进程(或者线程)之间存在一个统一编址的共享存储空间,程序员只需进行计算任务划分,不必进行数据划分,也不用确切地知道并行程序执行过程中进程间的通信。MPP(Massive Parallel Processing)系统和机群系统往往是消息传递系统。消息传递系统的可伸缩性通常比共享存储系统要好,可支持更多处理器。
从进程(或者线程)间通信的角度看,消息传递并行10.6程序比共享存储并行程序复杂一些,体现在时间管理和空间管理两方面。在空间管理方面,发送数据的进程需要关心自己产生的数据被谁用到,而接收数据的进程需要关心它用到了谁产生的数据;在时间管理方面,发送数据的进程通常需要在数据被接收后才能继续,而接收数据的进程通常需要等到接收数据后才能继续。在共享存储并行程序中,各进程间的通信通过访问共享存储器完成,程序员只需考虑进程间同步,不用考虑进程间通信。尤其是比较复杂的数据结构的通信,如struct{intpa;int pb;int*pc;},消息传递并行程序比共享存储并行程序复杂得多。此外,对于一些在编程时难以确切知道进程间通信的程序,用消息传递的方法很难进行并行化,如{for (i,j){ x=…; y=…; a[i][j]=b[x][y];}}。这段代码中,通信特征在程序运行时才能确定,编写代码时难以确定,改写成消息传递程序就比较困难。
@@ -561,14 +566,18 @@ div.csl-indent { \pi = 4\int_{0}^{1}{\frac{1}{1+x^2}}dx = \sum^{N}_{i=1}{\frac{4}{1+(\frac{i-0.5}{N})^2}\times{\frac{1}{N}}} \]在上式中,N值越大,误差越小。如果N值很大,计算时间就很长。可以通过并行处理,让每个进程计算其中的一部分,最后把每个进程计算的值加在一起来减少运算时间。图10.2给出了计算圆周率的共享存储(基于中科院计算所开发的JIAJIA虚拟共享存储系统)和消息传递并行程序核心片段的算法示意。该并行程序采用SPMD(Single Program Multiple Data)的模式,即每个进程都运行同一个程序,但处理不同的数据。在该程序中,numprocs是参与运算的进程个数,所有参与运算的进程都有相同的numprocs值;myid是参与运算的进程的编号,每个进程都有自己的编号(一般并行编程系统都会提供接口函数让进程知道自己的编号)。例如,如果有4个进程参与运算,则每个进程的numprocs都是4,而每个进程的myid号分别为0、1、2、3。在共享存储并行程序中,由jia_alloc()分配空间的变量pi是所有参与运算的进程共享的,所有进程只有一份,其他变量都是每个进程局部的,每个进程都有一份,每个进程根据numprocs和myid号分别计算部分的圆周率值,最后通过一个临界区的机制把所有进程的计算结果加在一起。jia_lock()和jia_unlock()是一种临界区的锁机制,保证每次只有一个进程进入这个临界区,这样才能把所有进程的结果依次加在一起,不会互相冲掉。在消息传递并行程序中,由malloc()分配空间的变量每个进程都有独立的一份,互相看不见。每个进程算完部分结果后,通过归约操作reduce()把所有进程的mypi加到0号进程的pi中。
-
-图 10.2: 积分求圆周率算法示意
+
++图 10.2: 积分求圆周率算法示意 +
第二个例子是矩阵乘法。矩阵乘法的算法大家都很熟悉,这里就不介绍了。图10.3给出了共享存储和消息传递并行程序。同样,由jia_alloc()分配的变量所有进程共享一份,而由malloc()分配的变量每个进程单独一份,因此在这个程序中消息传递并行程序需要更多的内存。在共享存储并行程序中,先由0号进程对A、B、C三个矩阵进行初始化,而其他进程通过jia_barrier()语句等待。barrier是并行程序中常用的同步方式,它要求所有进程都等齐后再前进。然后每个进程分别完成部分运算,再通过jia_barrier()等齐后由0号进程统一打印结果。消息传递并行程序与共享存储并行程序的最大区别是需要通过显式的发送语句send和接收语句recv进行多个进程之间的通信。先由0号进程进行初始化后发送给其他进程,每个进程分别算完后再发送给0号进程进行打印。在消息传递并行程序中要详细列出每次发送的数据大小和起始地址等信息,0号进程接收的时候还要把从其他进程收到的数据拼接在一个矩阵中,比共享存储并行程序麻烦不少。
-
-图 10.3: 矩阵乘法算法示意
+
++图 10.3: 矩阵乘法算法示意 +
图10.4简要示意了采用传统SISD指令和SIMD指令实现上述8个对应数组项求和的执行控制流程。
-
-图 10.4: SISD和SIMD执行控制流示意图
+
++图 10.4: SISD和SIMD执行控制流示意图 +
POSIX(Portable Operating System Interface)属于早期的共享存储编程模型。POSIXThreads()即Pthreads)代表官方IEEE POSIX1003.1C_1995线程标准,是由IEEE标准委员会所建立的,主要包含线程管理、线程调度、同步等原语定义,体现为C语言的一套函数库。下面只简介其公共性质。
1.线程管理
线程库用于管理线程,Pthreads中基本线程管理原语如下表所示。其中pthread_create()在进程内生成新线程,新线程执行带有变元arg的myroutine,如果pthread_create()生成,则返回0并将新线程之ID置入thread_id,否则返回指明错误类型的错误代码;pthread_exit()结束调用线程并执行清场处理;pthread_self()返回调用线程的ID;pthread_join()等待其他线程结束。
-
-图 10.5: 线程管理
+
++图 10.5: 线程管理 +
2.线程调度
pthread_yield()的功能是使调用者将处理器让位于其他线程;pthread_cancel()的功能是中止指定的线程。
@@ -804,8 +817,13 @@ lb $6, 0x0($src0) … }2.编译制导语句
-下面介绍编译制导语句的格式。参看前面的OpenMP程序并行结构的例子,在并行开始部分需要语句“#pragma omp parallel private(var1,var2) shared(var3)”。下表是编译制导语句的格式及解释。
-
++图 10.6: 编译制导语言 +
+3.并行域结构
一个并行域就是一个能被多个线程执行的程序块,它是最基本的OpenMP并行结构。并行域的具体格式为:
#pragma omp parallel [if(scalar_expression)| num_threads(integer-
@@ -816,9 +834,11 @@ lb $6, 0x0($src0)
当一个线程执行到parallel这个指令时,线程就会生成一列线程,线程号依次从0到n-1,而它自己会成为主线程(线程号为0)。当并行域开始时,程序代码就会被复制,每个线程都会执行该代码。这就意味着,到了并行域结束就会有一个栅障,且只有主线程能够通过这个栅障。
4.共享任务结构
共享任务结构将其内封闭的代码段划分给线程队列中的各线程执行。它不产生新的线程,在进入共享任务结构时不存在栅障,但是在共享任务结构结束时存在一个隐含的栅障。图10.7显示了3种典型的共享任务结构。其中:do/for将循环分布到线程列中执行,可看作是一种表达数据并行s的类型; sections把任务分割成多个各个部分(section),每个线程执行一个section,可很好地表达任务并行;single由线程队列中的一个线程串行执行。
-
-
-图 10.7: 共享任务类型
+
+
+
+图 10.7: 共享任务类型
+
下面具体来看一下。
1)for编译制导语句。for语句(即C/C++中的for语句),表明若并行域已经初始化了,后面的循环就在线程队列中并行执行,否则就会顺序执行。语句格式如下:
diff --git a/引言.html b/引言.html
index c435364..4f621c1 100644
--- a/引言.html
+++ b/引言.html
@@ -216,8 +216,9 @@ div.csl-indent {
- 2.5 RISC指令集比较
- 2.6 C语言的机器表示
@@ -290,6 +291,7 @@ div.csl-indent {
- 5.3.2 CPU-北桥-南桥三片结构
- 5.3.3 CPU-弱北桥-南桥三片结构
- 5.3.4 CPU-南桥两片结构
+- 5.3.5 SoC单片结构
- 5.4 处理器和IO设备间的通信
@@ -423,16 +425,17 @@ div.csl-indent {
- 11.2.3 Cache一致性协议
- 11.3 多核处理器的互连结构
-- 11.4 典型多核处理器
+
- 11.4 多核处理器的同步机制
+- 11.5 典型多核处理器
-- 11.5 本章小结
-- 11.6 习题
+- 11.6 本章小结
+- 11.7 习题
- VI 系统性能评价与分析
- 12 计算机系统性能评价与性能分析
@@ -508,9 +511,11 @@ div.csl-indent {
WPS被唤醒后处在运行状态。发现操作系统传过来的数据是个键盘输入空格,表示要翻页。WPS就把下一页要显示的内容准备好,调用操作系统中的显示驱动程序,把要显示的内容送到显存,由图形处理器(Graphic Processing Unit,简称GPU)通过访问显存空间刷新屏幕。达到了翻一页的效果。
再看一个问题:如果在翻页的过程中,发现翻页过程非常卡顿,即该计算机在WPS翻页时性能较低,可能是什么原因呢?首先得看看系统中有没有其他任务在运行,如果有很多任务在运行,这些任务会占用CPU、内存带宽、IO带宽等资源,使得WPS分到的资源不够,造成卡顿。如果系统中没有其他应用与WPS抢资源,还会卡顿,那是什么原因呢?多数人会认为是CPU太慢,需要升级。实际上,在WPS翻页时,CPU干的活不多。一种可能是下一页包含很多图形,尤其是很多矢量图,需要GPU画出来,GPU忙不过来了。另外一种可能是要显示的内容数据量大,要把大量数据从WPS的应用程序空间传给GPU使用的专门空间,内存带宽不足导致不能及时传输。在独立显存的情况下,数据如何从内存传输到显存有两种不同的机制:由CPU从内存读出来再写到显存需要CPU具有专门的IO加速功能,因为显存一般是映射在CPU的IO空间;不通过CPU,通过直接内存访问(Direct Memory Access,简称DMA)的方式直接从内存传输到显存会快得多。
“计算机体系结构”课程是研究怎么造计算机,而不是怎么用计算机。我们不是学习驾驶汽车,而是学习如何造汽车。一个计算机体系结构设计人员就像一个带兵打仗的将领,要学会排兵布阵。要上知天文、下知地理,否则就不会排兵布阵,或者只会纸上谈兵地排兵布阵,只能贻误军国大事。对计算机体系结构设计来说,“排兵布阵”就是体系结构设计,“上知天文”就是了解应用程序、操作系统、编译器的行为特征,“下知地理”就是了解逻辑、电路、工艺的特点。永远不要就体系结构论体系结构,要做到应用、系统、结构、逻辑、电路、器件的融会贯通。就像《论语》中说的“吾道一以贯之”。
-
-
-图 1.1: 通用计算机系统的层次结构
+
+
+
+图 1.1: 通用计算机系统的层次结构
+
图1.1给出了常见通用计算机系统的结构层次图。该图把计算机系统分成应用程序、操作系统、硬件系统、晶体管四个大的层次。注意把这四个层次联系起来的三个界面。第一个界面是应用程序编程接口API(Application Programming Interface),也可以称作“操作系统的指令系统”,介于应用程序和操作系统之间。API是应用程序的高级语言编程接口,在编写程序的源代码时使用。常见的API包括C语言、Fortran语言、Java语言、JavaScript语言接口以及OpenGL图形编程接口等。使用一种API编写的应用程序经重新编译后可以在支持该API的不同计算机上运行。所有应用程序都是通过API编出来的,在IT产业,谁控制了API谁就控制了生态,API做得好,APP(Application)就多。API是建生态的起点。第二个界面是指令系统ISA(Instruction Set Architecture),介于操作系统和硬件系统之间。常见的指令系统包括X86、ARM、MIPS、RISC-V和LoongArch等。指令系统是实现目标码兼容的关键,由于IT产业的主要应用都是通过目标码的形态发布的,因此ISA是软件兼容的关键,是生态建设的终点。指令系统除了实现加减乘除等操作的指令外,还包括系统状态的切换、地址空间的安排、寄存器的设置、中断的传递等运行时环境的内容。第三个界面是工艺模型,介于硬件系统与晶体管之间。工艺模型是芯片生产厂家提供给芯片设计者的界面,除了表达晶体管和连线等基本参数的SPICE(Simulation Program with Integrated Circuit Emphasis)模型外,该工艺所能提供的各种IP也非常重要,如实现PCIE接口的物理层(简称PHY)等。
需要指出的是,在API和ISA之间还有一层应用程序二进制接口(Application Binary Interface,简称ABI)。ABI是应用程序访问计算机硬件及操作系统服务的接口,由计算机的用户态指令和操作系统的系统调用组成。为了实现多进程访问共享资源的安全性,处理器设有“用户态”与“核心态”。用户程序在用户态下执行,操作系统向用户程序提供具有预定功能的系统调用函数来访问只有核心态才能访问的硬件资源。当用户程序调用系统调用函数时,处理器进入核心态执行诸如访问IO设备、修改处理器状态等只有核心态才能执行的指令。处理完系统调用后,处理器返回用户态执行用户代码。相同的应用程序二进制代码可以在相同ABI的不同计算机上运行。
@@ -527,19 +532,71 @@ div.csl-indent {
1.1.3 计算机的基本组成
我们从小就学习十进制的运算,0、1、2、3、4、5、6、7、8、9十个数字,逢十进一。计算机中使用二进制,只有0和1两个数字,逢二进一。为什么用二进制,不用我们习惯的十进制呢?因为二进制最容易实现。自然界中二值系统非常多,电压的高低、水位的高低、门的开关、电流的有无等等都可以组成二值系统,都可以用来做计算机。二进制最早是由莱布尼茨发明的,冯·诺依曼最早将二进制引入计算机的应用,而且计算机里面的程序和数据都用二进制。从某种意义上说,中国古人的八卦也是一种二进制。
计算机的组成非常复杂,但其基本单元非常简单。打开一台PC的机箱,可以发现电路板上有很多芯片。如图1.2所示,一个芯片就是一个系统,由很多模块组成,如加法器、乘法器等;而一个模块由很多逻辑门组成,如非门、与门、或门等;逻辑门由晶体管组成,如PMOS管和NMOS管等;晶体管则通过复杂的工艺过程形成。所以计算机是一个很复杂的系统,由很多可以存储和处理二进制运算的基本元件组成。就像盖房子一样,再宏伟、高大的建筑都是由基本的砖瓦、钢筋水泥等材料搭建而成的。在CPU芯片内部,一根头发的宽度可以并排走上千根导线;购买一粒大米的钱可以买上千个晶体管。
-
-
-图 1.2: 芯片、模块、逻辑门、晶体管和器件
+
+
+
+图 1.2: 芯片、模块、逻辑门、晶体管和器件
+
现在计算机结构的基本思想是1945年匈牙利数学家冯·诺依曼结合EDVAC计算机的研制提出的,因此被称为冯·诺依曼结构。
我们通过一个具体的例子来介绍冯·诺依曼结构。比如说求式子(3×4+5×7)的值,人类是怎么计算的呢?先计算3×4=12,把12记在脑子里,接着计算5×7=35,再计算12+35=47。我们在计算过程中计算和记忆(存储)都在一个脑袋里(但式子很长的时候需要把临时结果记在纸上)。
计算机的计算和记忆是分开的,负责计算的部分由运算器和控制器组成,称为中央处理器,就是CPU;负责记忆的部分称为存储器。存储器里存了两样东西,一是存了几个数,3、4、5、7、12、35、47,这个叫作数据;二是存储了一些指令。也就是说,操作对象和操作序列都保存在存储器里。
我们来看看计算机是如何完成(3×4+5×7)的计算的。计算机把3、4、5、7这几个数都存在内存中,计算过程中的临时结果(12、35)和最终结果(47)也存在内存中;此外,计算机还把对计算过程的描述(程序)也存在内存中,程序由很多指令组成。表1.1a给出了内存中在开始计算前数据和指令存储的情况,假设数据存在100号单元开始的区域,程序存在200号单元开始的区域。
-
-
-表 1.1: 程序和数据存储在一起
+
+
+表 1.1: 程序和数据存储在一起
-
+100
3
3
101
4
4
102
5
5
103
7
7
104
12
105
35
106
47
……
……
200
读取100号单元
读取100号单元
201
读取101号单元
读取101号单元
202
两数相乘
两数相乘
203
存入结果到104号单元
存入结果到104号单元
204
读取102号单元
读取102号单元
205
读取103号单元
读取103号单元
206
两数相乘
两数相乘
207
存入结果到105号单元
存入结果到105号单元
208
读取104号单元
读取104号单元
209
读取105号单元
读取105号单元
210
两数相加
两数相加
211
存入结果到106号单元
存入结果到106号单元
a)
b)
+
+
+
计算机开始运算过程如下:CPU从内存200号单元取回第一条指令,这条指令就是“读取100号单元”,根据这条指令的要求从内存把“3”读进来;再从内存201号单元取下一条指令“读取101号单元”,然后根据这条指令的要求从内存把“4”读进来;再从内存202号单元取下一条指令“两数相乘”,乘出结果为“12”;再从内存203号单元取下一条指令“存入结果到104号单元”,把结果“12”存入104号单元。如此往复直到程序结束。表1.1b是程序执行结束时内存的内容。
大家看看刚才这个过程,比我们大脑运算烦琐多了。我们大脑算三步就算完了,而计算机需要那么多步,又取指令又取数据,挺麻烦的。这就是冯·诺依曼结构的基本思想:数据和程序都在存储器中,CPU从内存中取指令和数据进行运算并把结果也放到内存中。把指令和数据都存在内存中可以让计算机按照事先规定的程序自动地完成运算,是实现图灵机的一种简单方法。冯·诺依曼结构很好地解决了自动化的问题:把程序放在内存里,一条条取进来,自己就做起来了,不用人来干预。如果没有这样一种自动执行的机制,让人去控制计算机做什么运算,拨一下开关算一下,程序没有保存在内存中而是保存在人脑中,就成算盘了。计算机的发展日新月异,但70多年过去了还是使用冯·诺依曼结构。尽管冯·诺依曼结构有很多缺点,例如什么都保存在内存中使访存成为性能瓶颈,但我们还是摆脱不了它。
虽然经过了长期的发展,以存储程序和指令驱动执行为主要特点的冯·诺依曼结构仍是现代计算机的主流结构。笔者面试研究生的时候经常问一个问题:冯·诺依曼结构最核心的思想是什么?结果很多研究生都会答错。有人说是由计算器、运算器、存储器、输入、输出五个部分组成;有人说是程序计数器导致串行执行;等等。实际上,冯·诺依曼结构就是数据和程序都存在存储器中,CPU从内存中取指令和数据进行运算,并且把结果也放在内存中。概括起来就是存储程序和指令驱动执行。
@@ -556,11 +613,61 @@ div.csl-indent {
编译器负责把用户用高级语言(如C、Java、JavaScript等)写的代码转换成计算机硬件能识别的、由一条条指令组成的二进制码。转换出来的目标码的质量的好坏在很大程度上影响完成一个任务的指令数。在同一台计算机上运行同一个应用程序,用不同的编译器或不同的编译选项,运行时间可能有几倍的差距。
指令系统的设计对完成一个任务的指令数影响也很大。例如要不要设计一条指令直接完成一个FFT函数,还是让用户通过软件的方法来实现FFT函数,这是结构设计的一个取舍,直接影响完成一个任务的指令数。体系结构有一个常用的指标叫MIPS(Million Instructions Per Second),即每秒执行多少百万条指令。看起来很合理的一个指标,关键是一条指令能干多少事讲不清楚。如果甲计算机一条指令就能做一个1024点的FFT,而乙计算机一条指令就算一个加法。两台计算机比MIPS值就没什么意义。因此后来有人把MIPS解释为Meaningless Indication of Processor Speed。现在常用一个性能指标MFLOPS(Million FLoating point Operations Per Second),即每秒做多少百万浮点运算,也有类似的问题。如果数据供不上,运算能力再强也没有用。
在指令系统确定后,结构设计需要重点考虑如何降低每条指令的平均执行周期(Cycles Per Instruction,简称CPI),或提高每个时钟周期平均执行的指令数(Instructions Per Cycle,简称IPC),这是处理器微结构研究的主要内容。CPI就是一个程序执行所需要的总的时钟周期数除以它所执行的总指令数,反之则是IPC。处理器的微结构设计对IPC的影响很大,采用单发射还是多发射结构,采用何种转移猜测策略以及什么样的存储层次设计都直接影响IPC。表1.2给出了龙芯3A1000和龙芯3A2000处理器运行SPEC CPU2000基准程序的分值。两个CPU均为64位四发射结构,主频均为1GHz,两个处理器运行的二进制码相同,但由于微结构不同,IPC差异很大,总体上说,3A2000的IPC是3A1000的2~3倍。
-
-
-表 1.2: 龙芯3A1000和龙芯3A2000的SPEC CPU2000分值
+
+
+表 1.2: 龙芯3A1000和龙芯3A2000的SPEC CPU2000分值
-
+SPEC程序
3A1000
3A2000
运行时间/秒
分值
运行时间/秒
分值
164.gzip
503
279
323
433
175.vpr
389
360
222
632
176.gcc
206
533
110
1,003
181.mcf
480
375
195
925
186.crafty
166
604
122
822
197.parser
707
254
266
676
252.eon
159
815
141
924
253.perlbmk
418
431
279
644
254.gap
338
325
155
711
255.vortex
291
652
125
1,520
256.bzip2
383
391
285
527
300.twolf
421
712
364
824
SPEC_INT2000
447
764
168.wupwise
338
473
123
1,296
171.swim
1,299
239
324
957
172.mgrid
1,045
172
169
1,062
173.applu
900
233
197
1,067
177.mesa
244
574
156
896
178.galgel
507
572
143
2,022
179.art
173
1,504
97
2,686
183.equake
457
285
96
1,353
187.facerec
288
659
146
1,306
188.ammp
538
409
274
803
189.lucas
716
279
181
1,104
191.fma3d
550
382
203
1,034
200.sixtrack
553
199
276
399
301.apsi
1,159
224
235
1,108
SPEC_FP2000
367
1,120
+
+
+
主频宏观上取决于微结构设计,微观上取决于工艺和电路设计。例如Pentium III的流水线是10级,Pentium IV为了提高主频,一发猛就把流水级做到了20级,还恨不得做到40级。Intel的研究表明,只要把Cache和转移猜测表的容量增加一倍,就能抵消流水线增加一倍引起的流水线效率降低。又如,从电路的角度来说,甲设计做64位加法只要1ns,而乙设计需要2ns,那么甲设计比乙设计主频高一倍。相同的电路设计,用不同的工艺实现出来的主频也不一样,先进工艺晶体管速度快,主频高。
可见在一个系统中不同层次有不同的性能标准,很难用一项单一指标刻画计算机性能的高低。大家可能会说,从应用的角度看性能是最合理的。甲计算机两个小时算完明天的天气预报,乙计算机只要一小时,那乙的性能肯定比甲的好,这总对吧。也对也不对。只能说,针对算明天的天气预报这个应用,乙计算机的性能比甲的好。但对于其他应用,甲的性能可能反而比乙的好。
@@ -575,9 +682,11 @@ div.csl-indent {
1.2.3 计算机的功耗
计算机的第三个重要指标是功耗。手机等移动设备需要用电池供电。电池怎么用得久呢?低功耗就非常重要。高性能计算机也要低功耗,它们的功耗都以兆瓦(MW)计。兆瓦是什么概念?我们上大学时在宿舍里煮方便面用的电热棒的功率是1000W左右,几个电热棒一起用宿舍就停电了。1MW就是1000个电热棒的功率。曙光5000高性能计算机在中科院计算所的地下室组装调试时,运行一天电费就是一万多块钱,比整栋楼的电费还要高。计算机里产生功耗的地方非常多,CPU有功耗,内存条有功耗,硬盘也有功耗,最后为了把这些热量散发出去,制冷系统也要产生功耗。近几年来,性能功耗比(Performance per Watt)成为计算机非常重要的一个指标。
芯片功耗是计算机功耗的重要组成部分。芯片的功耗主要由晶体管工作产生,所以先来看晶体管的功耗组成。图1.3是一个反相器的功耗模型。反相器由一个PMOS管和一个NMOS管组成。其功耗主要可以分为三类:开关功耗、短路功耗和漏电功耗。开关功耗主要是电容的充放电,比如当输出端从0变到1时,输出端的负载电容从不带电变为带电,有一个充电的过程;当输出端从1变到0时,电容又有一个放电的过程。在充电、放电的过程中就会产生功耗。开关功耗既和充放电电压、电容值有关,还和反相器开关频率相关。
-
-
-图 1.3: 动态功耗和短路功耗
+
+
+
+图 1.3: 动态功耗和短路功耗
+
短路功耗就是P管和N管短路时产生的功耗。当反相器的输出为1时,P管打开,N管关闭;输出为0时,则N管开,P管闭。但在开、闭的转换过程中,电流的变化并不像理论上那样是一个方波,而是有一定的斜率。在这个变化的过程中会出现N管和P管同时部分打开的情况,这时候就产生了短路功耗。
漏电功耗是指MOS管不能严格关闭时发生漏电产生的功耗。以NMOS管为例,如果栅极有电N管就导通;否则N管就关闭。但在纳米级工艺下,MOS管沟道很窄,即使栅极不加电压,源极和漏极之间也有电流;另外栅极下的绝缘层很薄,只有几个原子的厚度,从栅极到沟道也有漏电流。漏电流大小随温度升高呈指数增加,因此温度是集成电路的第一杀手。
@@ -600,14 +709,18 @@ div.csl-indent {
1.3.1 摩尔定律和工艺的发展
1.工艺技术的发展
摩尔定律不是一个客观规律,是一个主观规律。摩尔是Intel公司的创始人,他在20世纪六七十年代说集成电路厂商大约18个月能把工艺提高一代,即相同面积中晶体管数目提高一倍。大家就朝这个目标去努力,还真做到了。所以摩尔定律是主观努力的结果,是投入很多钱才做到的。现在变慢了,变成2~3年或更长时间更新一代,一个重要原因是新工艺的研发成本变得越来越高,厂商收回投资需要更多的时间。摩尔定律是计算机体系结构发展的物质基础。正是由于摩尔定律的发展,芯片的集成度和运算能力都大幅度提高。图1.4通过一些历史图片展示了国际上集成电路和微处理器的发展历程。
-
-
-图 1.4: 集成电路和微处理器的发展历程
+
+
+
+图 1.4: 集成电路和微处理器的发展历程
+
图1.5给出了由我国自行研制的部分计算机和微处理器的历史图片。可以看出,随着工艺技术的发展,计算机从一个大机房到一个小芯片,运算能力大幅度提高,这就是摩尔定律带来的指数式发展的效果。其中的109丙机值得提一下,这台机器为“两弹一星”的研制立下了汗马功劳,被称为功勋机。
-
-
-图 1.5: 我国自行研制的计算机和微处理器
+
+
+
+图 1.5: 我国自行研制的计算机和微处理器
+
CMOS工艺正在面临物理极限。在21世纪之前的35年(或者说在0.13μm工艺之前),半导体场效应晶体管扩展的努力集中在提高器件速度以及集成更多的器件和功能到芯片上。21世纪以来,器件特性的变化和芯片功耗密度成为半导体工艺发展的主要挑战。随着线宽尺度的不断缩小,CMOS的方法面临着原子和量子机制的边界。一是蚀刻等问题越来越难处理,可制造性问题突出;二是片内漂移的问题非常突出,同一个硅片内不同位置的晶体管都不一样;三是栅氧(晶体管中栅极下面作为绝缘层的氧化层)厚度难以继续降低,65nm工艺的栅氧厚度已经降至了1.2nm,也就是五个硅原子厚,漏电急剧增加,再薄的话就短路了,无法绝缘了。
工程师们通过采用新技术和新工艺来克服这些困难并继续延续摩尔定律。在90/65nm制造工艺中,采用了多项新技术和新工艺,包括应力硅(Strained Silicon)、绝缘硅(SOI)、铜互连、低k(k指介电常数)介电材料等。45/32nm工艺所采用的高k介质和金属栅材料技术是晶体管工艺技术的又一个重要突破。采用高k介质(SiO2的k为3.9,高k材料的介电常数在20以上)如氧氮化铪硅(HfSiON)理论上相当于提升栅极的有效厚度,使漏电电流下降到10%以下。另外高k介电材料和现有的硅栅电极并不相容,采用新的金属栅电极材料可以增加驱动电流。该技术打通了通往32nm及22nm工艺的道路,扫清工艺技术中的一大障碍。摩尔称此举是CMOS工艺技术中的又一里程碑,将摩尔定律又延长了另一个10~15年。Intel公司最新CPU上使用的三维晶体管FinFET,为摩尔定律的发展注入了新的活力。
@@ -660,11 +773,61 @@ div.csl-indent {
1.4.1 平衡性
结构设计的第一个原则就是要考虑平衡性。一个木桶所盛的水量的多少由最短的木板决定,一个结构最终体现出的性能受限于其瓶颈部分。计算机是个复杂系统,影响性能的因素很多。例如,一台个人计算机使用起来比较卡顿,一般人会觉得主要是由于CPU性能不够,实际上真正引起性能卡顿的可能是内存带宽、硬盘或网络带宽、GPU性能,或者是CPU和GPU之间数据传输不顺,等等。又如,一般的CPU微结构研究专注于其中某些重要因素如Cache命中率和转移猜测命中率的改善,但通用CPU微结构中影响性能的因素非常复杂,重排序缓冲项数、发射队列项数、重命名寄存器个数、访存队列项数、失效队列项数、转移指令队列项数与一级Cache失效延迟、二级Cache失效延迟、三级Cache失效延迟等需要平衡设计,有关队列大小应保证一级Cache和二级Cache的失效不会引起流水线的堵塞。
通用CPU设计有一个关于计算性能和访存带宽平衡的经验原则,即峰值浮点运算速度(MFLOPS)和峰值访存带宽(MB/s)为1∶1左右。表1.3给出了部分典型CPU的峰值浮点运算速度和访存带宽比。从表中可以看出,一方面,最新的CPU峰值浮点运算速度和访存带宽比逐步增加,说明带宽已经成为通用CPU的重要瓶颈,多核的发展是有限度的;另一方面,如果去除SIMD(Single Instruction Multiple Data)的因素,即去除128位SIMD浮点峰值为64位浮点的2倍,256位SIMD浮点峰值为64位浮点的4倍的因素,则浮点峰值和访存带宽还是基本保持着1∶1的关系,因为SIMD一般只有科学计算使用,一般的事务处理不会用SIMD的浮点性能。
-
-
-表 1.3: 典型CPU的浮点峰值和访存带宽比
+
+
+表 1.3: 典型CPU的浮点峰值和访存带宽比
-
+CPU
年代
主频
SIMD
GFLOPS
GB/s
含SIMD比例
无SIMD比例
DEC Alpha 21264
1,996
600MHz
-
1.2
2.0
0.60
0.60
AMD K7 Athlon
1,999
700MHz
-
1.4
1.6
0.88
0.88
Intel Pentium III
1,999
600MHz
-
0.6
0.8
0.75
0.75
Intel Pentium IV
2,001
1.5GHz
-
3.0
3.2
0.94
0.94
Intel Core2 E6420 X2
2,007
2.8GHz
128位
22.4
8.5
2.64
1.32
AMD K10 Phenom II X4 955
2,009
3.2GHz
128位
51.2
21.3
2.40
1.20
Intel Nehalem X5560
2,009
2.8GHz
128位
44.8
32.0
1.40
0.70
IBM Power8
2,014
5.0GHz
128位
480.0
230.4
2.08
1.04
AMD Piledriver Fx8350
2,014
4.0GHz
256位
128.0
29.9
4.29
1.07
Intel Skylake E3-1230 V5
2,015
3.4GHz
256位
217.6
34.1
6.38
1.60
龙芯3A2000
2,015
1.0GHz
-
16.0
16.0
1.00
1.00
龙芯3A5000
2,020
2.5GHz
256位
160.0
51.2
3.13
0.78
+
+
+
计算机体系结构中有一个著名的Amdahl定律。该定律指出通过使用某种较快的执行方式所获得的性能的提高,受限于不可使用这种方式提高性能的执行时间所占总执行时间的百分比,例如一个程序的并行加速比,最终受限于不能被并行化的串行部分。也就是性能的提升不仅跟其中的一些指令的运行时间的优化有关,还和这些指令在总指令数中所占的比例有关:
\[ ExTime_{new} = Extime_{old} * \left((1 - Fraction_{enhanced}) + \frac{Fraction_{enhanced}}{Speedup_{enhanced}}\right) \]
\[ Speedup_{overall} = \frac{Extime_{old}}{ExTime_{new}} \]
diff --git a/总结.html b/总结.html
index 32c1cd6..221cb81 100644
--- a/总结.html
+++ b/总结.html
@@ -216,8 +216,9 @@ div.csl-indent {
- 2.5 RISC指令集比较
- 2.6 C语言的机器表示
@@ -290,6 +291,7 @@ div.csl-indent {
- 5.3.2 CPU-北桥-南桥三片结构
- 5.3.3 CPU-弱北桥-南桥三片结构
- 5.3.4 CPU-南桥两片结构
+- 5.3.5 SoC单片结构
- 5.4 处理器和IO设备间的通信
@@ -423,16 +425,17 @@ div.csl-indent {
- 11.2.3 Cache一致性协议
- 11.3 多核处理器的互连结构
-- 11.4 典型多核处理器
+
- 11.4 多核处理器的同步机制
+- 11.5 典型多核处理器
-- 11.5 本章小结
-- 11.6 习题
+- 11.6 本章小结
+- 11.7 习题
- VI 系统性能评价与分析
- 12 计算机系统性能评价与性能分析
diff --git a/指令流水线.html b/指令流水线.html
index fc1bc77..07b9135 100644
--- a/指令流水线.html
+++ b/指令流水线.html
@@ -216,8 +216,9 @@ div.csl-indent {
- 2.5 RISC指令集比较
- 2.6 C语言的机器表示
@@ -290,6 +291,7 @@ div.csl-indent {
- 5.3.2 CPU-北桥-南桥三片结构
- 5.3.3 CPU-弱北桥-南桥三片结构
- 5.3.4 CPU-南桥两片结构
+- 5.3.5 SoC单片结构
- 5.4 处理器和IO设备间的通信
@@ -423,16 +425,17 @@ div.csl-indent {
- 11.2.3 Cache一致性协议
- 11.3 多核处理器的互连结构
-- 11.4 典型多核处理器
+
- 11.4 多核处理器的同步机制
+- 11.5 典型多核处理器
-- 11.5 本章小结
-- 11.6 习题
+- 11.6 本章小结
+- 11.7 习题
- VI 系统性能评价与分析
- 12 计算机系统性能评价与性能分析
@@ -503,20 +506,26 @@ div.csl-indent {
5)运算器,用于执行指令所指示的运算操作;
6)数据存储器,按照地址存储数据,主要用于访存指令。
将这些组成要素通过一定规则连接起来,就形成了CPU的数据通路。图9.1给出了这个简单CPU的数据通路。
-
-
-图 9.1: 简单CPU的数据通路
+
+
+
+图 9.1: 简单CPU的数据通路
+
数据通路上各组成要素间的具体连接规则如下:根据PC从指令存储器中取出指令,然后是译码部件解析出相关控制信号,并读取通用寄存器堆;运算器对通用寄存器堆读出的操作数进行计算,得到计算指令的结果写回通用寄存器堆,或者得到访存指令的地址,或者得到转移指令的跳转目标;load指令访问数据存储器后,需要将结果写回通用寄存器堆。通用寄存器堆写入数据在计算结果和访存结果之间二选一。由于有控制流指令的存在,因此新指令的PC既可能等于顺序下一条指令的PC(当前指令PC加4),也可能来自转移指令计算出的跳转目标。
译码部件在这个数据通路中有非常重要的作用。译码部件要识别不同的指令,并根据指令要求,控制读取哪些通用寄存器、执行何种运算、是否要读写数据存储器、写哪个通用寄存器,以及根据控制流指令来决定PC的更新。这些信息从指令码中获得,传递到整个处理器中,控制了处理器的运行。根据LoongArch指令的编码格式,可以将指令译码为op、src1、src2、src3、dest和imm几个部分,示例见图9.2。
-
-
-图 9.2: 译码功能示意
+
+
+
+图 9.2: 译码功能示意
+
图9.3展示了带控制逻辑的数据通路,图中虚线是新加入的控制逻辑。此外,还加入了时钟和复位信号。引入时钟是因为更新PC触发器、写通用寄存器以及store类访存指令写数据存储器时都需要时钟。而引入复位信号是为了确保处理器每次上电后都是从相同位置取回第一条指令。数据通路再加上这些逻辑,就构成了处理器。
-
-
-图 9.3: 带有时序控制逻辑的数据通路
+
+
+
+图 9.3: 带有时序控制逻辑的数据通路
+
简要描述一下这个处理器的执行过程:
1)复位信号将复位的PC装载到PC触发器内,之后的第一个时钟周期内,使用PC取指、译码、执行、读数据存储器、生成结果;
@@ -528,9 +537,11 @@ div.csl-indent {
9.2 流水线处理器
根据8.1.2小节给出的CMOS电路延迟的介绍,当电路中组合逻辑部分延迟增大时,整个电路的频率就会变低。在上一节的单周期处理器模型中,每个时钟周期必须完成取指、译码、读寄存器、执行、访存等很多组合逻辑工作,为了保证在下一个时钟上升沿到来之前准备好寄存器堆的写数据,需要将每个时钟周期的间隔拉长,导致处理器的主频无法提高。使用流水线技术可以提高处理器的主频。在引入流水线技术之前,先介绍一下多周期处理器的概念。
在单周期处理器中,每个时钟周期内执行的功能可以比较明显地进行划分。举例而言,按照取指、译码并读寄存器、执行、访存和准备写回划分为5个阶段。如果我们在每段操作前后加上触发器,看起来就能减少每个时钟周期的工作量,提高处理器频率。在图9.4中,加粗框线的是触发器。
-
-
-图 9.4: 多周期处理器的结构图
+
+
+
+图 9.4: 多周期处理器的结构图
+
为了清晰,图中省略了控制逻辑的部分连线,没有画出通用寄存器和数据存储器的写入时钟。先将原始时钟接到所有的触发器,按照这个示意图设计的处理器是否可以使用呢?按照时序逻辑特性,每个时钟上升沿,触发器的值就会变成其驱动端D端口的新值,因此推算一下:
1)在第1个时钟周期,通过PC取出指令,在第2个时钟上升沿锁存到指令码触发器R1;
@@ -539,27 +550,35 @@ div.csl-indent {
推算到这里就会发现,此时离控制逻辑的生成(第2个时钟周期)已经隔了一个时钟周期了,怎么保证这时候控制逻辑还没有发生变化呢?
使用分频时钟或门控时钟可以做到这一点。如图9.4右下方所示,将原始的时钟通过分频的方式产生出5个时钟,分别控制图中PC、R1~R4这5组触发器。这样,在进行ALU运算时,可以保证触发器R1没有接收到下一个时钟上升沿,故不可能变化,因此可以进行正确的ALU运算。同理,包括写寄存器、执行访存等,都受到正确的控制。
经过推算,可以将这种处理器执行指令时的指令-时钟周期的对照图画出来,如9.5。这种图可以被称为处理器执行的时空图,也被称为流水线图。画出流水线图是分析处理器行为的直观、有效的方法。
-
-
-图 9.5: 多周期处理器的流水线时空图
+
+
+
+图 9.5: 多周期处理器的流水线时空图
+
这种增加触发器并采用分频时钟的处理器模型称为多周期处理器。多周期处理器设计可以提高运行频率,但是每条指令的执行时间并不能降低(考虑到触发器的Setup时间和Clk-to-Q延迟则执行时间会增加)。我们可以将各个执行阶段以流水方式组织起来,同一时刻不同指令的不同执行阶段(流水线中的“阶段”也称为“级”)重叠在一起,进一步提高CPU执行效率。
从多周期处理器演进到流水线处理器,核心在于控制逻辑和数据通路对应关系维护机制的变化。多周期处理器通过使用分频时钟,可以确保在同一条指令的后面几个时钟周期执行时,控制逻辑因没有接收到下一个时钟上升沿所以不会发生变化。流水线处理器则通过另一个方法来保证这一点,就是在每级流水的触发器旁边,再添加一批用于存储控制逻辑的触发器。指令的控制逻辑藉由这些触发器沿着流水线逐级传递下去,从而保证了各阶段执行时使用的控制逻辑都是属于该指令的,如9.6所示。
-
-
-图 9.6: 流水线处理器的结构图
+
+
+
+图 9.6: 流水线处理器的结构图
+
从图9.6中的虚线可以看出,控制运算器进行计算的信息来自控制逻辑2,即锁存过一次的控制逻辑,刚好与R2中存储的运算值同属一条指令。图中取消了R3阶段写通用寄存器的通路,而是将R3的内容锁存一个时钟周期,统一使用控制逻辑4和R4来写。
可以先设计几条简单指令,画出时空图,看看这个新的处理器是如何运行的。示例见图9.7。
-
-
-图 9.7: 流水线处理器的流水线时空图
+
+
+
+图 9.7: 流水线处理器的流水线时空图
+
要记得图中R2、R3和R4实际上还包括各自对应的控制逻辑触发器,所以到下一个时钟周期后,当前部件及对应触发器已经不再需要给上一条指令服务,新的指令才可以在下一个时钟周期立即占据当前的触发器。
如果从每个处理器部件的角度,也可以画出另一个时空图,见图9.8。图中不同下标的I代表不同的指令。
-
-
-图 9.8: 处理器部件时空图
+
+
+
+图 9.8: 处理器部件时空图
+
从这个角度看过去,处理器的工作方式就像一个5人分工合作的加工厂,每个工人做完自己的部分,将自己手头的工作交给下一个工人,并取得一个新的工作,这样可以让每个工人都一直处于工作状态。这种工作方式被称为流水线,采用这种模型的处理器被称为流水线处理器。
@@ -578,32 +597,42 @@ div.csl-indent {
ld.w $r4, $r3, 0
add.w $r5, $r4, $r4
其中第1、2条指令间,第2、3条指令间,第3、4条指令间存在RAW相关。这3条指令在9.2节所介绍的5级简单流水线处理器中执行的流水线时空图如图9.9所示。
-
-
-图 9.9: RAW数据相关的流水线时空图
+
+
+
+图 9.9: RAW数据相关的流水线时空图
+
图9.9中从第1条指令的写回阶段指向第2条指令的译码阶段的箭头以及从第2条指令的写回阶段指向第3条指令的译码阶段的箭头都表示RAW相关会引起冲突。这是因为如果第2条指令要使用第1条指令写回到寄存器的结果,就必须保证第2条指令读取寄存器的时候第1条指令的结果已经写回到寄存器中了,而现有的5级流水线结构如果不加控制,第2条指令就会在第1条指令写回寄存器之前读取寄存器,从而引发数据错误。为了保证执行的正确,一种最直接的解决方式是让第2条指令在译码阶段等待(阻塞)3拍,直到第1条指令将结果写入寄存器后才能读取寄存器,进入后续的执行阶段。同样的方式亦适用于第2、3条指令之间和第3、4条指令之间。采用阻塞解决数据相关的流水线时空图如图9.10所示。
-
-
-图 9.10: 用阻塞解决数据相关的流水线时空图
+
+
+
+图 9.10: 用阻塞解决数据相关的流水线时空图
+
阻塞功能在处理器流水线中的具体电路实现是:将被阻塞流水级所在的寄存器保持原值不变,同时向被阻塞流水级的下一级流水级输入指令无效信号,用流水线空泡(Bubble)填充。对于图9.10所示的流水线阻塞,从每个处理器部件的角度所看到的时空图如图9.11所示。
-
-
-图 9.11: 有阻塞的处理器部件时空图
+
+
+
+图 9.11: 有阻塞的处理器部件时空图
+
9.3.1.1 流水线前递技术
采用阻塞的方式虽然能够解决RAW相关所引发的流水线冲突,但是阻塞势必引起流水线执行效率的降低,为此需要更为高效的解决方式。继续分析前面所举的例子,可以发现第2条指令位于译码阶段的时候,虽然它所需要的第1条指令的结果还不在寄存器中,但是这个值已经在流水线的执行阶段计算出来了,那么何必非要等着这个值沿着流水线一级一级送下去写入寄存器后再从寄存器中读出呢?直接把这个值取过来用不也是可行的吗?顺着这个思路就产生了流水线前递(Forwarding)技术。其具体实现是在流水线中读取指令源操作数的地方通过多路选择器直接把前面指令的运算结果作为后面指令的输入。考虑到加法指令在执行级就完成了运算,因此能够设计一条通路,将这个结果前递至读寄存器的地方,即有一条从执行级到译码级的前递通路。除此之外,还可以依次添加从访存级、写回级到译码级的前递通路。新的流水线时空图如图9.12所示。
-
-
-图 9.12: 加入前递的数据相关时空图
+
+
+
+图 9.12: 加入前递的数据相关时空图
+
可以看出,加入前递技术之后,执行这4条指令的性能有大幅提高。
通过前面对于指令相关的分析,我们需要在处理器中加入阻塞流水线的控制逻辑以及前递通路。演进后的处理器结构如图9.13所示。为了表达清晰,图中省略了时钟信号到每组触发器的连接线。
-
-
-图 9.13: 处理指令相关的流水线结构图
+
+
+
+图 9.13: 处理指令相关的流水线结构图
+
图9.13中虚线框中是新加入的逻辑。为了解决数据相关,加入了寄存器相关判断逻辑,收集当前流水线中处于执行、访存及写回级的最多3条指令的目的寄存器信息,与译码级的源寄存器比较,并根据比较结果决定是否阻塞译码级R1;为了解决控制相关,加入了译码级和执行级能够修改PC级有效位的通路;为了解决结构相关,加入了译码级到PC级的阻塞控制逻辑;为了支持前递,加入了从执行级、访存级到译码级的数据通路,并使用寄存器相关判断逻辑来控制如何前递。可以看出,大多数机制都加在了前两级流水线上。
@@ -611,19 +640,25 @@ div.csl-indent {
9.3.2 控制相关引发冲突及解决方法
控制相关引发的冲突本质上是对程序计数器PC的冲突访问引起的。图9.14中的箭头即表示控制相关所引发的冲突。
-
-
-图 9.14: 控制相关示意图
+
+
+
+图 9.14: 控制相关示意图
+
按照图9.6给出的处理器设计,执行阶段R2触发器所存储的值经过计算之后才能给出转移指令的正确目标并在下一个时钟上升沿更新PC,但是图9.13中转移指令尚未执行结束时,PC已经更新完毕并取指了,从而可能导致取回了错误的指令。为了解决这个问题,可以通过在取指阶段引入2拍的流水线阻塞来解决,如图9.15所示。
-
-
-图 9.15: 解决控制相关的流水线结构图
+
+
+
+图 9.15: 解决控制相关的流水线结构图
+
在单发射5级静态流水线中,如果增加专用的运算资源将转移指令条件判断和计算下一条指令PC的处理调整到译码阶段,那么转移指令后面的指令只需要在取指阶段等1拍。采用这种解决控制相关的方式,继续改进流水线处理器结构,得到如图9.16所示的结构设计。
-
-
-图 9.16: 处理控制相关的流水线结构图
+
+
+
+图 9.16: 处理控制相关的流水线结构图
+
为更进一步减少由控制相关引起的阻塞,可以采用转移指令的延迟槽技术,在定义指令系统的时候就明确转移指令延迟槽指令的执行不依赖于转移指令的结果,这样转移指令后面的指令在取指阶段1拍也不用等。总之,在单发射5级静态流水线处理器中,通过在译码阶段对转移指令进行处理和利用转移指令延迟槽技术,就可以避免控制相关引起的流水线阻塞。但是这两项技术并不一定适用于其他结构,后面9.5.3小节讨论转移预测技术时将做进一步分析。
@@ -654,9 +689,11 @@ div.csl-indent {
9.5.1 多发射数据通路
我们首先讨论如何降低理想CPI。最直观的方法就是让处理器中每级流水线都可以同时处理更多的指令,这被称为多发射数据通路技术。例如双发射流水线意味着每一拍用PC从指令存储器中取两条指令,在译码级同时进行两条指令的译码、读源寄存器操作,还能同时执行两条指令的运算操作和访存操作,并同时写回两条指令的结果。那么双发射流水线的理想CPI就从单发射流水线的1降至0.5。
要在处理器中支持多发射,首先就要将处理器中的各种资源翻倍,包括采用支持双端口的存储器。其次还要增加额外的阻塞判断逻辑,当同一个时钟周期执行的两条指令存在指令相关时,也需要进行阻塞。包括数据相关、控制相关和结构相关在内的阻塞机制都需要改动。我们来观察几条简单指令在双发射流水线中的时空图,如图9.17所示。
-
-
-图 9.17: 双发射处理器的流水线时空图
+
+
+
+图 9.17: 双发射处理器的流水线时空图
+
在图9.17中,为了流水线控制的简化,只有同一级流水线的两条指令都不被更早的指令阻塞时,才能让这两条指令一起继续执行,所以第6条指令触发了陪同阻塞。
多发射数据通路技术虽然从理论上而言可以大幅度降低处理器的CPI,但是由于各类相关所引起的阻塞影响,其实际执行效率是要大打折扣的。所以我们还要进一步从减少各类相关引起的阻塞这个方面入手来提高流水线的执行效率。
@@ -670,9 +707,11 @@ div.csl-indent {
由于除法单元采用迭代算法实现,所以div.w指令需要多个执行周期,与它有RAW相关的add.w指令最早也只能等到div.w指令执行完毕后才能开始执行。但是sub.w指令何时可以开始执行呢?可以看到sub.w指令与前两条指令没有任何相关,采用动态调度的流水线就允许sub.w指令越过前面尚未执行完毕的div.w指令和add.w指令,提前开始执行。因为sub.w是在流水线由于指令间的相关引起阻塞而空闲的情况下“见缝插针”地提前执行了,所以这段程序整体的执行延迟就减少了。
要完成上述功能,需要对原有的流水线做一些改动。首先,要将原有的译码阶段拆分成“译码”和“读操作数”两个阶段。译码阶段进行指令译码并检查结构相关,随后在读操作数阶段则一直等待直至操作数可以读取。处在等待状态的指令不能一直停留在原有的译码流水级上,因为这样它后面的指令就没法前进甚至是进入流水线,更不用说什么提前执行了。所以我们会利用一个结构存放这些等待的指令,这个结构被称为保留站,有的文献中也称之为发射队列,这是动态调度中必需的核心部件。除了存储指令的功能,保留站还要负责控制其中的指令何时去执行,因此保留站中还会记录下描述指令间相关关系的信息,同时监测各条指令的执行状态。如果指令是在进入保留站前读取寄存器,那么保留站还需要监听每条结果总线,获得源操作数的最新值。
保留站在处理器中的大致位置如图9.18中的虚线框所示。保留站按指令来组织,每个指令占据一项,每一项有多个域,存放这个指令所需要的所有信息,包括有效位、就绪位异常信息、控制信息和寄存器源操作数。译码并读寄存器的指令进入保留站,保留站会每个时钟周期选择一条没有被阻塞的指令,送往执行逻辑,并退出保留站,这个动作称为“发射”。
-
-
-图 9.18: 动态调度流水线结构示意
+
+
+
+图 9.18: 动态调度流水线结构示意
+
保留站调度算法的核心在于“挑选没有被阻塞的指令”。从保留站在流水线所处的位置来看,保留站中的指令不可能因为控制相关而被阻塞。结构相关所引起的阻塞的判定条件也是直观的,即检查有没有空闲的执行部件和空闲的发射端口。但是在数据相关所引起的阻塞的处理上,存在着不同的设计思路。
为了讨论清楚保留站如何处理数据相关所引起的阻塞,先回顾一下9.3节关于RAW、WAR、WAW三种数据相关的介绍,在那里我们曾提到在5级简单流水线上WAR和WAW两种数据相关不会产生冲突,但是在动态调度的情况下就可能会产生。下面来看两个例子。例如下面的指令序列:
@@ -717,17 +756,21 @@ div.csl-indent {
下面通过一个简单的例子来说明转移预测对性能的影响。假设平均8条指令中有1条转移指令,某处理器采用4发射结构,在第10级流水计算出转移方向和目标(这意味着转移预测失败将产生(10-1)×4=36个空泡)。如果不进行转移预测而采用阻塞的方式,那么取指带宽浪费36/(36+8)=82%;如果进行简单的转移预测,转移预测错误率为50%,那么平均每16条指令预测错误一次,指令带宽浪费36/(36+16)=75%;如果使用误预测率为10%的转移预测器,那么平均每80条指令预测错误一次,指令带宽浪费降至36/(36+80)=31%;如果使用误预测率为4%的转移预测器,则平均每200条指令预测错误一次,取指令带宽浪费进一步降至36/(36+200)=15%。
从上面的例子可以看出,在转移预测错误开销固定的情况下,提高转移预测的准确率有助于大幅度提升处理器性能。那么能否设计出具有很高预测正确率的转移预测器呢?通过对大量应用程序中转移指令的行为进行分析后,人们发现它具有两个非常好的特性:首先,转移指令有较好的局部性,即少数转移指令的执行次数占所有转移指令执行次数中的绝大部分,这意味着只要对少量高频次执行的转移指令做出准确的预测就能获得绝大多数的性能提升;其次,绝大多数转移指令具有可预测性,即能够通过对转移指令的行为进行分析学习得出其规律性。
转移指令的可预测性主要包括单条转移指令的重复性以及不同转移指令之间存在的方向相关、路径相关。单条转移指令的重复性主要与程序中的循环有关。例如for型循环中转移指令的模式为TT……TN(成功n次后跟1次不成功);while型循环中转移指令的模式为NN……NT(不成功n次后跟1次成功)。不同转移指令之间的相关性主要出现在“if…else…”结构中。图9.19(a)是转移指令之间存在方向相关的例子。两个分支的条件(完全或部分)基于相同或相关的信息,后面分支的结果基于前面分支的结果。图9.19(b)是转移指令之间存在路径相关的例子。如果一个分支是通向当前分支的前n条分支之一,则称该分支处在当前分支的路径上,处在当前分支的路径上的分支与当前分支结果之间的相关性称为路径相关。
-
-
-图 9.19: 转移指令之间的相关性
+
+
+
+图 9.19: 转移指令之间的相关性
+
下面具体介绍一种最基本的转移预测结构,即根据单条转移指令的转移历史来进行转移预测。这种转移预测主要依据转移指令重复性的规律,对于重复性特征明显的转移指令(如循环)可以取得很好的预测效果。例如,对于循环语句for (i=0; i<10; i++) {…},可以假设其对应的汇编代码中是由一条回跳的条件转移指令来控制循环的执行。该转移指令前9次跳转,第10次不跳转,如果我们用1表示跳转,0表示不跳转,那么这个转移指令的转移模式就记为(1111111110)。这个转移模式的特点是,如果上一次是跳转,那么这一次也是跳转的概率比较大。这个特点启发我们将该转移指令的执行历史记录下来用于猜测该转移指令是否跳转。这种用于记录转移指令执行历史信息的表称为转移历史表(Branch History Table, 简称BHT)。最简单的BHT利用PC的低位进行索引,每项只有1位,记录索引到该项的转移指令上一次执行时的跳转情况,1表示跳转,0表示不跳转。由于存储的信息表征了转移的模式,所以这种BHT又被称为转移模式历史表(Pattern History Table, 简称PHT)。利用这种1位PHT进行预测时,首先根据转移指令的PC低位去索引PHT,如果表项值为1,则预测跳转,否则预测不跳转;其次要根据该转移指令实际的跳转情况来更新对应PHT的表项中的值。仍以前面的for循环为例,假设PHT的表项初始值都为0,那么转移指令第1次执行时,读出的表项为0所以预测不跳转,但这是一次错误的预测,第1次执行结束时会根据实际是跳转的结果将对应的表项值更新为1;转移指令第2次执行时,从表项中读出1所以预测跳转,这是一次正确的预测,第2次执行结束时会根据实际是跳转的结果将对应的表项值更新为1;……;转移指令第10次执行时,从表项中读出1所以预测跳转,这是一次错误的预测,第10次执行结束时会根据实际是不跳转的结果将对应的表项值更新为0。可以看到进入和退出循环都要猜错一次。这种PHT在应对不会多次执行的单层循环时,或者循环次数特别多的循环时还比较有效。但是对于如下的两重循环:
for (i=0; i<10; i++) for (j=0; j<10; j++) {…}
使用上述1位PHT,则内外循环每次执行都会猜错2次,这样总的转移预测正确率仅有80%。
为了提高上述情况下的转移预测正确率,可以采用每项2位的PHT。这种PHT中每一项都是一个2位饱和计数器,相应的转移指令每次执行跳转就加1(加到3为止),不跳转就减1(减到0为止)。预测时,如果相应的PHT表项的高位为1(计数器的值为2或3)就预测跳转,高位为0(计数器的值为0或1)就预测不跳转。也就是说,只有连续两次猜错,才会改变预测的方向。使用上述2位PHT后,前面两重循环的例子中,内层循环的预测正确率从80%提高到(7+81)/100=88%。图9.20给出了2位PHT转移预测机制的示意。
-
-
-图 9.20: 2位PHT原理
+
+
+
+图 9.20: 2位PHT原理
+
还有很多技术可以提高分支预测的准确率。可以使用分支历史信息与PC进行哈希操作后再查预测表,让分支历史影响预测结果;可以使用多个预测器同时进行预测,并预测哪个预测器的结果更准确,这被称为锦标赛预测器。具体的实现方法,以及更高级的分支预测技术可以参见本套系列教材中的硕士课程教材。
@@ -736,9 +779,11 @@ div.csl-indent {
9.5.4 高速缓存
由于物理实现上存在差异,自20世纪80年代以来CPU和内存的速度提升幅度一直存在差距,而且这种差距随着时间的推移越来越大。例如DDR3内存的访问延迟约为50ns,而高端处理器的时钟周期都在1ns以下,相当于每访问一次DDR3都需要花费至少50个处理器的时钟周期,如果程序有较多依赖访存结果的数据相关,就会严重影响处理器的性能。处理器和内存的速度差距造就了存储层次:离处理器流水线距离越近的地方,使用存储密度小的电路结构,牺牲存储容量来换取更快的访问速度;离处理器流水线距离越远的地方,使用存储密度大的电路结构,牺牲访问速度来换取存储容量。目前计算机中常见的存储层次包括寄存器、高速缓存(Cache)、内存、IO这四个层次。本节主要讨论Cache的相关概念。
Cache为了追求访问速度,容量通常较小,其中存放的内容只是主存储器内容的一个子集。Cache是微体系结构的概念,它没有程序上的意义,没有独立的编址空间,处理器访问Cache和访问存储器使用的是相同的地址,因而Cache对于编程功能正确性而言是透明的。Cache在流水线中的位置大致如图9.21所示,这里为了避免共享Cache引入的结构相关采用了独立的指令Cache和数据Cache,前者仅供取指,后者仅供访存。
-
-
-图 9.21: Cache在流水线结构图中的示意
+
+
+
+图 9.21: Cache在流水线结构图中的示意
+
由于Cache没有独立的编址空间,且只能存放一部分内存的内容,所以一个Cache单元可能在不同时刻存储不同的内存单元的内容。这就需要一种机制来标明某个Cache单元当前存储的是哪个内存单元的内容。因此Cache的每一个单元不仅要存储数据,还要存储该数据对应的内存地址(称为Cache标签,Tag)以及在Cache中的状态(如是否有效,是否被改写等)。
处理器访问Cache时,除了用其中的某些位进行索引外,还要将访问地址与Cache中的Tag相比较。如果命中,则直接对Cache中的内容进行访问;如果不命中,则该指令阻塞在取指或者访存阶段,同时由Cache失效处理逻辑完成后续处理后才能继续执行,如果是读访问那么就需要从下一层存储中取回所需读取的数据,并放置在Cache中。
@@ -747,14 +792,18 @@ div.csl-indent {
2)Cache与下一层存储的数据关系,即写策略,分为写穿透和写回两种。存数指令需要修改下一层存储的值,如果将修改后的值暂时放在Cache中,当Cache替换回下一层存储时再写回,则称为写回Cache;如果每条存数指令都要立即更新下一层存储的值,则称为写穿透Cache。
3)Cache的替换策略,分为随机替换、LRU替换和FIFO替换。当发生Cache失效而需要取回想要的Cache行,此时如果Cache满了,则需要进行替换。进行Cache替换时,如果有多个Cache行可供替换,可以选择随机进行替换,也可以替换掉最先进入Cache的Cache行(FIFO替换),或者替换掉最近最少使用的Cache行(LRU替换)。
直接相联、全相联和组相联中内存和Cache的映射关系原理如图9.22所示。将内存和Cache都分为大小一样的块,假设内存有32项,Cache有8项。在直接相联方式中,每个内存块只能放到Cache的一个位置上,假设要把内存的第12号块放到Cache中,因为Cache只有8项,所以只能放在第(12 mod 8=4)项上,其他地方都不能放;由此可知第4、12、20、28号内存块都对应到Cache的第4项上,如果冲突了就只能替换。这就是直接相联,硬件简单但效率低,如图9.22(a)所示。在全相联方式中,每个内存块都可以放到Cache的任一位置上,这样第4、12、20、28号内存块可以同时放入Cache中。这就是全相联,硬件复杂但效率高,如图9.22(b)所示。组相联是直接相联和全相联的折中。以两路组相联为例,Cache中第0、2、4、6号位置为一路(这里称为第0路),第1、3、5、7为另一路(这里称为第1路),每路4个Cache块。对于内存的第12号块,因为12除以4余数为0,所以既可以把第12号块放到Cache第0路的第0号位置(即Cache的第0号位置),也可以放到第1路的第0号位置(即Cache的第1号位置),如图9.22(c)所示。
-
-
-图 9.22: 直接相联、全相联、组相联映射
+
+
+
+图 9.22: 直接相联、全相联、组相联映射
+
直接相联、全相联和组相联Cache的结构如图9.23所示。从中可以看出,访问Cache时地址可分为3个部分:偏移(Offset)、索引(Index)和标签(Tag)。Offset是块内地址,在地址的低位。因为Cache块一般比较大,通常包含32字节或64字节,而指令或数据访问往往没有这么宽,需要通过Offset来指定访问对象在块内的具体位置。Index是用来索引Cache块的,将其作为地址来访问Cache。地址的高位是访问Cache的Tag,用于和Cache中保存的Tag进行比较,如果相等就给出命中信号Hit。在直接相联结构中,访问地址的Tag仅需要和Index索引的那个Cache块的Tag比较;在全相联结构中,Index位数为0,访问地址的Tag需要和每个Cache块的Tag比较,如果相等就给出命中信号Hit,同时将命中项的Cache块的Data通过Mux(多路选择器,Multiplexer)选出;在组相联结构中,访问地址的Tag需要和每一组中Index索引的那个Cache块的Tag比较,生成Hit信号并选出命中项的Data。注意Offset位数只和Cache块大小相关,但Tag和Index位数则和相联度相关。例如在32位处理器中,如果Cache大小为16KB,块大小为32字节,则Offset为5位,共有512个Cache块。采用直接相联结构Index为9位,Tag为18位;采用全相联结构Index为0位,Tag为27位;采用两路组相联结构Index为8位,Tag为19位。
-
-
-图 9.23: 直接相联、全相联、组相联Cache结构
+
+
+
+图 9.23: 直接相联、全相联、组相联Cache结构
+
在基本Cache结构的基础之上,有着一系列围绕性能的优化技术,具体可以参见本套系列教材中的硕士课程教材。
@@ -763,9 +812,11 @@ div.csl-indent {
9.6 本章小结
本章从处理器的数据通路开始,先引入流水线技术,并逐渐增加设计复杂度,最终搭建出了5级静态流水线处理器。本章还简要介绍了一些提高流水线效率的方法。
图9.24是龙芯3A2000处理器的流水线示意图。
-
-
-图 9.24: 龙芯3A2000流水线示意图
+
+
+
+图 9.24: 龙芯3A2000流水线示意图
+
可以看出,现代处理器依然没有脱离教材中讲述的基础原理。图中左侧为PC级和译码级,并加入了分支预测、指令Cache和指令TLB;图的中间部分为重命名和提交单元,重命名后指令进入保留站,也称发射队列,并在就绪后发射并执行;图的右侧为访存执行单元,需要访问数据Cache和数据TLB,并有可能访问图下方的二级Cache。提交单元要负责将指令提交,提交后指令就可以退出流水线了。
diff --git a/推荐序.html b/推荐序.html
index ee2564a..a978177 100644
--- a/推荐序.html
+++ b/推荐序.html
@@ -216,8 +216,9 @@ div.csl-indent {
- 2.5 RISC指令集比较
- 2.6 C语言的机器表示
@@ -290,6 +291,7 @@ div.csl-indent {
- 5.3.2 CPU-北桥-南桥三片结构
- 5.3.3 CPU-弱北桥-南桥三片结构
- 5.3.4 CPU-南桥两片结构
+- 5.3.5 SoC单片结构
- 5.4 处理器和IO设备间的通信
@@ -423,16 +425,17 @@ div.csl-indent {
- 11.2.3 Cache一致性协议
- 11.3 多核处理器的互连结构
-- 11.4 典型多核处理器
+
- 11.4 多核处理器的同步机制
+- 11.5 典型多核处理器
-- 11.5 本章小结
-- 11.6 习题
+- 11.6 本章小结
+- 11.7 习题
- VI 系统性能评价与分析
- 12 计算机系统性能评价与性能分析
@@ -500,7 +503,7 @@ div.csl-indent {
第二个特点是强调“一以贯之”的系统性。“计算机系统结构”的关键词是“系统”而不是“结构”,国外做计算机系统结构研究的学者介绍自己时往往是说:“我是做系统(System)研究的。”计算机专业的学生应具有系统层面的理解能力,能站在系统的高度解决应用问题。对计算机系统是否有全面深入的了解是区别计算机专业人才和非专业人才的重要标志。长期以来我们采用“解剖学”的思路进行计算机教学,按照硬件、软件、应用等分类横切成几门相对独立的课程,使得计算机系毕业的学生对整个计算机系统缺乏完整的理解。如果问已经学完全部计算机课程的学生,在键盘上敲一个空格键到屏幕上的PPT翻一页,在这一瞬间计算机中哪些硬件和软件在运转,如何运转,可能绝大多数学生都讲不清楚。本书有若干章节专门讲述计算机的软硬件协同、计算机系统的启动过程等,着力培养学生的全局思维能力。为了使学生一开始就对计算机有全局的框架性认识,此教材的第1章对全书内容做了尽可能通俗易懂的描述,这是追求系统性教学的刻意安排。本书作者强调:“一个计算机体系结构设计人员就像一个带兵打仗的将领,要学会排兵布阵。要上知天文、下知地理,否则就不会排兵布阵,或者只会纸上谈兵地排兵布阵,只能贻误军国大事。”这里讲的“天文”是指应用程序、编译程序和操作系统,“地理”是指逻辑、电路和工艺。只有上下贯通,才能真正掌握计算机体系结构。
第三个特点是强调能在硅上实现的实践性。由于CMOS电路集成度的指数性提高,一块CPU芯片已可以集成几十亿晶体管。计算机体系结构的许多知识现在都体现在CPU中,因此从某种意义上讲,不懂CPU设计就不能真正明白计算机体系结构的奥妙。CPU的结构通常称为微体系结构,主要在硕士课程中讲授,但本科生的体系结构课程也应学习在硅上能实现的技术。陆游诗云:“纸上得来终觉浅,绝知此事要躬行。”只会P2P的学习(从Paper到Paper的学习)往往学不到真本事,只有最后能“躬行”到硅上的知识才是过硬的知识。本书作者有十几年从事CPU设计的经验,能正确区分哪些是纸上谈兵的知识,哪些是能落实到硅上的知识,这是他们独特的优势。在中国科学院大学的本科教学中,计算机体系结构课程还辅以高强度的实验课,实践证明这对学生真正理解课堂学到的知识大有好处。
本书内容选材还需要经过课堂教学的长期检验,需要不断听取学生的反馈意见和同行的批评建议,希望经过几年的完善修改,本书能真正成为受到众多大学普遍欢迎的精品教材。
-
+
diff --git a/相关资源.html b/相关资源.html
index 92a9d45..0ccefbc 100644
--- a/相关资源.html
+++ b/相关资源.html
@@ -216,8 +216,9 @@ div.csl-indent {
- 2.5 RISC指令集比较
- 2.6 C语言的机器表示
@@ -290,6 +291,7 @@ div.csl-indent {
- 5.3.2 CPU-北桥-南桥三片结构
- 5.3.3 CPU-弱北桥-南桥三片结构
- 5.3.4 CPU-南桥两片结构
+- 5.3.5 SoC单片结构
- 5.4 处理器和IO设备间的通信
@@ -423,16 +425,17 @@ div.csl-indent {
- 11.2.3 Cache一致性协议
- 11.3 多核处理器的互连结构
-- 11.4 典型多核处理器
+
- 11.4 多核处理器的同步机制
+- 11.5 典型多核处理器
-- 11.5 本章小结
-- 11.6 习题
+- 11.6 本章小结
+- 11.7 习题
- VI 系统性能评价与分析
- 12 计算机系统性能评价与性能分析
diff --git a/第三版序.html b/第三版序.html
index 8b4eed1..d150fac 100644
--- a/第三版序.html
+++ b/第三版序.html
@@ -216,8 +216,9 @@ div.csl-indent {
- 2.5 RISC指令集比较
- 2.6 C语言的机器表示
@@ -290,6 +291,7 @@ div.csl-indent {
- 5.3.2 CPU-北桥-南桥三片结构
- 5.3.3 CPU-弱北桥-南桥三片结构
- 5.3.4 CPU-南桥两片结构
+- 5.3.5 SoC单片结构
- 5.4 处理器和IO设备间的通信
@@ -423,16 +425,17 @@ div.csl-indent {
- 11.2.3 Cache一致性协议
- 11.3 多核处理器的互连结构
-- 11.4 典型多核处理器
+
- 11.4 多核处理器的同步机制
+- 11.5 典型多核处理器
-- 11.5 本章小结
-- 11.6 习题
+- 11.6 本章小结
+- 11.7 习题
- VI 系统性能评价与分析
- 12 计算机系统性能评价与性能分析
diff --git a/自序.html b/自序.html
index a56c0bf..59f620a 100644
--- a/自序.html
+++ b/自序.html
@@ -216,8 +216,9 @@ div.csl-indent {
- 2.5 RISC指令集比较
- 2.6 C语言的机器表示
@@ -290,6 +291,7 @@ div.csl-indent {
- 5.3.2 CPU-北桥-南桥三片结构
- 5.3.3 CPU-弱北桥-南桥三片结构
- 5.3.4 CPU-南桥两片结构
+- 5.3.5 SoC单片结构
- 5.4 处理器和IO设备间的通信
@@ -423,16 +425,17 @@ div.csl-indent {
- 11.2.3 Cache一致性协议
- 11.3 多核处理器的互连结构
-- 11.4 典型多核处理器
+
- 11.4 多核处理器的同步机制
+- 11.5 典型多核处理器
-- 11.5 本章小结
-- 11.6 习题
+- 11.6 本章小结
+- 11.7 习题
- VI 系统性能评价与分析
- 12 计算机系统性能评价与性能分析
diff --git a/计算机启动过程分析.html b/计算机启动过程分析.html
index e0254f5..270b6ef 100644
--- a/计算机启动过程分析.html
+++ b/计算机启动过程分析.html
@@ -216,8 +216,9 @@ div.csl-indent {
- 2.5 RISC指令集比较
- 2.6 C语言的机器表示
@@ -290,6 +291,7 @@ div.csl-indent {
- 5.3.2 CPU-北桥-南桥三片结构
- 5.3.3 CPU-弱北桥-南桥三片结构
- 5.3.4 CPU-南桥两片结构
+- 5.3.5 SoC单片结构
- 5.4 处理器和IO设备间的通信
@@ -423,16 +425,17 @@ div.csl-indent {
- 11.2.3 Cache一致性协议
- 11.3 多核处理器的互连结构
-- 11.4 典型多核处理器
+
- 11.4 多核处理器的同步机制
+- 11.5 典型多核处理器
-- 11.5 本章小结
-- 11.6 习题
+- 11.6 本章小结
+- 11.7 习题
- VI 系统性能评价与分析
- 12 计算机系统性能评价与性能分析
@@ -552,9 +555,11 @@ lu52i.d $r2, $r2, 0x900
这几条指令对处理器核的中断处理相关寄存器进行了初始化,并对后续软件将使用的栈地址等进行了初始化。第一条csrxchg指令将例外配置寄存器(0x4偏移)中的比特18∶16设置为0,以将除TLB外的所有例外和中断入口设置为同一个(代码中的0x1C001000)。第一条csrwr指令将该例外入口地址(0xC号控制寄存器)设置为0x1C001000,第二条csrwr指令将TLB重填例外的入口地址(0x88号控制寄存器)也设置为0x1C001000。实际上BIOS并没有使用TLB地址映射,一旦出现了TLB重填例外,一定是使用的地址出现了错误。第二条csrxchg指令将模式信息寄存器(0x0号控制寄存器)中的比特2设置为0,以禁用所有的中断。可以看到,对于stack、_gp这些地址的装载所用的la指令,在经过编译器编译之后,最终产生了多条指令与之对应。其中lu12i.w用于将20位立即数符号扩展并装载到寄存器的比特63∶12,lu32i.d用于将20位立即数符号扩展并装载到寄存器的比特63∶32,lu52i.d用于将12位立即数装载到寄存器的比特63∶52,ori用于将12位立即数与寄存器的内容进行或操作。
需要指出的是,处理器复位后先是通过频率为几十兆赫兹(MHz)以下的低速设备取指令,例如SPI或LPC等接口。一拍只能取出1比特(SPI)或4比特(LPC),而一条指令一般需要32比特。对于吉赫兹(GHz)的高性能处理器来说,几千拍才能执行一条指令,相当于在城市空荡荡的大街上只有一个人在行走,这时候的指令很“孤独”。
-
-
-图 7.1: 系统复位到操作系统启动的简要流程图
+
+
+
+图 7.1: 系统复位到操作系统启动的简要流程图
+
整个处理器由系统复位到操作系统启动的简要流程如图7.1所示。其中第一列为处理器核初始化过程,第二列为芯片核外部分初始化过程,第三列为设备初始化过程,第四列为内核加载过程,第五列为多核芯片中的从核(Slave Core)独有的启动过程。
@@ -564,11 +569,77 @@ lu52i.d $r2, $r2, 0x900
对串口的初始化操作实际上是处理器对串口执行一连串约定好的IO操作。在X86结构下,IO地址空间与内存地址空间相互独立,IO操作与访存操作是通过不同的指令实现的。MIPS和LoongArch等结构并不显式区分IO和内存地址,而是采用全局编址,使用地址空间将IO和内存隐式分离,并通过地址空间或TLB映射对访问方式进行定序及缓存等的控制。只有理解IO与内存访问的区别,才能很好地理解计算机启动中的各种初始化过程。
内存空间对应的是存储器,存储器不会发生存储内容的自行更新。也就是说,如果处理器核向存储单元A中写入了0x5a5a的数值,除非有其他的主控设备(例如其他的处理器核或是其他的设备DMA)对它也进行写入操作,否则这个0x5a5a的数值是不会发生变化的。
IO空间一般对应的是控制寄存器或状态寄存器,是受IO设备的工作状态影响的。此时,写入的数据与读出的数据可能会不一致,而多次读出的数据也可能不一致,其读出数据是受具体设备状态影响的。例如,对串口的线路状态寄存器(寄存器偏移0x5)的读取在不同的情况下会产生不同的返回值。该寄存器定义如表7.1所示。
-
-
-表 7.1: 串口线路状态寄存器定义
+
+
+表 7.1: 串口线路状态寄存器定义
-
+位域
位域名称
位宽
访问
描述
7
ERROR
1
R
错误表示位
+
‘1’ – 至少有奇偶校验位错误,帧错误或打断中断的一个。
+
‘0’–没有错误
6
TE
1
R
传输为空表示位
+
‘1’ – 传输FIFO和传输移位寄存器都为空。给传输FIFO写数据时清零
+
‘0’–有数据
5
TFE
1
R
传输FIFO 位空表示位
+
‘1’ – 当前传输FIFO为空,给传输FIFO写数据时清零
+
‘0’–有数据
4
BI
1
R
打断中断表示位
+
‘1’ –接收到起始位+数据+奇偶位+停止位都是0,即有打断中断
+
‘0’–没有打断
3
FE
1
R
帧错误表示位
+
‘1’ – 接收的数据没有停止位
+
‘0’–没有错误
2
PE
1
R
奇偶校验位错误表示位
+
‘1’ – 当前接收数据有奇偶错误
+
‘0’–没有奇偶错误
1
OE
1
R
数据溢出表示位
+
‘1’ – 有数据溢出
+
‘0’–无溢出
0
DR
1
R
接收数据有效表示位
+
‘0’ – 在FIFO中无数据
+
‘1’–在FIFO中有数据
+
+
+
可以看到这个寄存器里的各个数据位都与当时的设备状态相关。例如当程序等待外部输入数据时,就需要查询这个寄存器的第0位,以确定是否收到数据,再从FIFO寄存器中读取实际的数据。在这个轮询的过程中,寄存器的第0位根据串口的工作状态由0变成1。
更有意思的是,这个寄存器的某些位在读操作之后会产生自动清除的效果,例如第7位(错误表示位)在一次读操作之后会自动清零。
从这个寄存器上可以看到IO访问与内存访问的一些区别。IO寄存器的行为与具体的设备紧密相关,每种IO设备都有各自不同的寄存器说明,需要按照其规定的访问方式进行读写,而不像内存可以进行随意的读写操作。
@@ -601,11 +672,61 @@ lu52i.d $r2, $r2, 0x900
nop
END(initserial)
这里有一个值得注意的地方,串口设备使用相同的地址映射了两套功能完全不同的寄存器,通过线路控制寄存器的最高位(就是串口寄存器中偏移为3的寄存器的最高位)进行切换。因为其中一套寄存器主要用于串口波特率的设置,只需要在初始化时进行访问,在正常工作状态下完全不用再次读写,所以能够将其访问地址与另一套正常工作用的寄存器相复用来节省地址空间。表7.2中是两组不同寄存器的定义。
-
-
-表 7.2: 串口的部分地址复用寄存器
+
+
+表 7.2: 串口的部分地址复用寄存器
-
+偏移
名称(初始化设置下,0x3[7] = 1)
名称(工作模式下,0x3[7] = 0)
0x0
分频锁存器低位
数据寄存器
0x1
分频锁存器高位
中断使能寄存器
0x2
读的时候为中断标识寄存器,写的时候为FIFO控制寄存器
0x3
线路控制寄存器。其中比特7为分频控制访问使能。该位为1时可以访问表中“初始化设置”寄存器,为0时访问表中“工作模式”寄存器
+
+
+
在初始化时,代码中先将0x3偏移寄存器的最高位设置为1,以访问分频设置寄存器,按照与连接设备协商好的波特率和字符宽度,将初始化信息写入配置寄存器中。然后退出分频寄存器的访问模式,进入正常工作模式。
在使用时,串口的对端是一个同样的串口,两个串口的发送端和接收端分别对连,通过双向的字符通信来实现被调试机的字符输出和字符输入功能。
在正常工作模式下,当CPU需要通过串口对外发送和接收字符时执行的两个函数分别如下:
@@ -692,9 +813,11 @@ END(CPU_TLBClear)
不同的处理器可能包含不同的Cache层次,各级Cache的容量也可能各不相同。例如龙芯3A1000处理器包含私有一级指令Cache、私有一级数据Cache和共享二级Cache两个层次,而龙芯3A5000处理器则包含私有一级指令Cache、私有一级数据Cache、私有二级替换Cache和共享三级Cache三个层次。在进行Cache初始化时要考虑所有需要的层次。
Cache的组织结构主要包含标签(Tag)和数据(Data)两个部分,Tag用于保存Cache块状态、Cache块地址等信息,Data则保存数据内容。大多数情况下对Cache的初始化就是对Tag的初始化,只要将其中的Cache块状态设置为无效,其他部分的随机数据就不会产生影响。
龙芯3A5000中一级数据Cache的组织如图7.2所示。
-
-
-图 7.2: 龙芯3A5000的一级数据Cache组织
+
+
+
+图 7.2: 龙芯3A5000的一级数据Cache组织
+
其中Tag上的cs位为0表示该Cache块为无效状态,对该Cache的初始化操作就是使用Cache指令将Tag写为0。对应的ECC位,会在Tag写入时自动生成,不需要专门处理。
不同Cache层次中Tag的组织结构可能会略有区别,初始化程序也会稍有不同,在此不一一列举。以下仅以龙芯3A处理器内部的一级指令Cache的初始化为例进行说明。
@@ -774,26 +897,38 @@ END(godson2_cache_init)
在PCI协议下,IO的系统空间分为三个部分:配置空间、IO空间和Memory空间。配置空间存储设备的基本信息,主要用于设备的探测和发现;IO空间比较小,用于少量的设备寄存器访问;Memory空间可映射的区域较大,可以方便地映射设备所需要的大块物理地址空间。
对于X86架构来说,IO空间的访问需要使用IO指令操作,Memory空间的访问则需要使用通常的load/store指令操作。而对于MIPS或者LoongArch这种把设备和存储空间统一编址的体系结构来说,IO空间和Memory空间没有太大区别,都使用load/store指令操作。IO空间与Memory空间的区别仅在于所在的地址段不同,对于某些设备的Memory访问,可能可以采用更长的单次访问请求。例如对于IO空间,可以限制为仅能使用字访问,而对于Memory空间,则可以任意地使用字、双字甚至更长的Cache行访问。
配置空间的地址偏移由总线号、设备号、功能号和寄存器号的组合得到,通过对这个组合的全部枚举,可以很方便地检测到系统中存在的所有设备。
-以HyperTransport总线为例,配置访问分为两种类型,即Type0和Type1,其区别在于基地址和对总线号的支持。如图7.3所示,只需要在图中总线号、设备号、功能号的位置上进行枚举,就可以遍历整个总线,检测到哪个地址上存在设备。
-
-通过这种方式,即使在某次上电前总线上的设备发生了变化,也可以在这个枚举的过程中被探测到。而每个设备都拥有唯一的识别号,即图7.4中的设备号和厂商号,通过加载这些识别号对应的驱动,就完成了设备的自动识别和驱动的自动加载。
-图7.4为标准的设备配置空间寄存器分布。对于所有设备,这个空间的分布都是一致的,以保证PCI协议对其进行统一的检索。
-
-图7.4中的厂商识别号(Vendor ID)与设备识别号(Device ID)的组合是唯一的,由专门的组织进行管理。每一个提供PCI设备的厂商都应该拥有唯一的厂商识别号,以在设备枚举时正确地找到其对应的驱动程序。例如英特尔的厂商识别号为0x8086,龙芯的厂商识别号为0x0014。设备识别号对于每一个设备提供商的设备来说应该是唯一的。这两个识别号的组合就可以在系统中唯一地指明正确的驱动程序。
+以HyperTransport总线为例,配置访问分为两种类型,即Type0和Type1,其区别在于基地址和对总线号的支持。如图7.3所示,只需要在图中总线号、设备号、功能号的位置上进行枚举,就可以遍历整个总线,检测到哪个地址上存在设备。
+
+
+
+图 7.3: HyperTransport总线配置访问的两种类型
+
+
+通过这种方式,即使在某次上电前总线上的设备发生了变化,也可以在这个枚举的过程中被探测到。而每个设备都拥有唯一的识别号,即图7.4中的设备号和厂商号,通过加载这些识别号对应的驱动,就完成了设备的自动识别和驱动的自动加载。
+图7.4为标准的设备配置空间寄存器分布。对于所有设备,这个空间的分布都是一致的,以保证PCI协议对其进行统一的检索。
+
+
+
+图 7.4: 标准的设备配置空间寄存器分布
+
+
+图7.4中的厂商识别号(Vendor ID)与设备识别号(Device ID)的组合是唯一的,由专门的组织进行管理。每一个提供PCI设备的厂商都应该拥有唯一的厂商识别号,以在设备枚举时正确地找到其对应的驱动程序。例如英特尔的厂商识别号为0x8086,龙芯的厂商识别号为0x0014。设备识别号对于每一个设备提供商的设备来说应该是唯一的。这两个识别号的组合就可以在系统中唯一地指明正确的驱动程序。
除了通过厂商识别号与设备识别号对设备进行识别并加载驱动程序之外,还可以通过设备配置空间寄存器中的类别代码(Class Code)对一些通用的设备进行识别,并加载通用驱动。例如USB接口所使用的OHCI(Open Host Controller Interface,用于USB2.0 Full Speed或其他接口)、EHCI(Enhanced Host Controller Interface,用于USB2.0 High Speed)、XHCI(eXtensible Host Controller Interface,用于USB3.0),SATA接口所使用的AHCI(Advance Host Controller Interface,用于SATA接口)等。这一类通用接口控制器符合OHCI、EHCI、XHCI或AHCI规范所规定的标准接口定义和操作方法,类似于处理器的指令集定义,只要符合相应的规范,即使真实的设备不同,也能够运行标准的驱动程序。
所谓驱动程序就是一组函数,包含用于初始化设备、关闭设备或是使用设备的各种相关操作。还是以最简单的串口设备为例,如果在设备枚举时找到了一个PCI串口设备,它的驱动程序里面可能包含哪些函数呢?首先是初始化函数,在找到设备后,首先执行一次初始化函数,以使设备到达可用状态。然后是发送数据函数和接收数据函数。在Linux内核中,系统通过调用读写函数接口实现真正的设备操作。在发送数据函数和接收数据函数中,需要将设备发送数据和接收数据的方法与读写函数的接口相配合,这样在系统调用串口写函数时,能够通过串口发送数据,调用串口读函数时,能够得到串口接收到的数据。此外还有中断处理函数,当串口中断发生时,让中断能够进入正确的处理函数,通过读取正确的中断状态寄存器,找到中断发生的原因,再进行对应的处理。
当然,为了实现所有设备的共同工作,还需要其他PCI协议特性的支持。
-首先就是对于设备所需IO空间和Memory空间的灵活设置。从图7.4可以看到,在配置空间中,并没有设备本身功能上所使用的寄存器。这些寄存器实际上是由可配置的IO空间或Memory空间来索引的。
-图7.4的配置空间中存在6组独立的基址寄存器(Base Address Registers,简称BAR)。这些BAR一方面用于告诉软件该设备所需要的地址空间类型及其大小,另一方面用于接收软件给其配置的基地址。
+首先就是对于设备所需IO空间和Memory空间的灵活设置。从图7.4可以看到,在配置空间中,并没有设备本身功能上所使用的寄存器。这些寄存器实际上是由可配置的IO空间或Memory空间来索引的。
+图7.4的配置空间中存在6组独立的基址寄存器(Base Address Registers,简称BAR)。这些BAR一方面用于告诉软件该设备所需要的地址空间类型及其大小,另一方面用于接收软件给其配置的基地址。
BAR的寄存器定义如图7.5所示,其最低位表示该BAR是IO空间还是Memory空间。BAR中间有一部分只读位为0,正是这些0的个数表示该BAR所映射空间的大小,也就是说BAR所映射的空间为2的幂次方大小。BAR的高位是可写位,用来存储软件设置的基地址。
-
-
-图 7.5: BAR的寄存器定义
+
+
+
+图 7.5: BAR的寄存器定义
+
在这种情况下,对一个BAR的基地址配置方式首先是确定BAR所映射空间的大小,再分配一个合适的空间,给其高位基地址赋值。确定BAR空间大小的方法也很巧妙,只要给这个寄存器先写入全1的值,再读出来观察0的个数即可得到。
对PCI设备的探测和驱动加载是一个递归调用过程,大致算法如下:
1)将初始总线号、初始设备号、初始功能号设为0。
-2)使用当前的总线号、设备号、功能号组成一个配置空间地址,这个地址的构成如图7.3所示,使用该地址,访问其0号寄存器,检查其设备号。
+2)使用当前的总线号、设备号、功能号组成一个配置空间地址,这个地址的构成如图7.3所示,使用该地址,访问其0号寄存器,检查其设备号。
3)如果读出全1或全0,表示无设备。
4)如果该设备为有效设备,检查每个BAR所需的空间大小,并收集相关信息。
5)检测其是否为一个多功能设备,如果是则将功能号加1再重复扫描,执行第2步。
@@ -867,17 +1002,117 @@ void _pci_query_dev_func(struct pci_device *dev, pcitag tag, int initialise)
}
}
假设Memory空间的起始地址为0x40000000,在设备扫描过程中发现了USB控制器、显示控制器和网络控制器,三个设备对于Memory空间的需求如表7.3所示。
-
-
-表 7.3: 三个设备的空间需求
+
+
+表 7.3: 三个设备的空间需求
-
+设备号
名称
BAR号
大小
1
USB控制器
0
4KB
2
显示控制器
0
128MB
1
64KB
3
网络控制器
0
4KB
1
16KB
+
+
+
在得到以上信息后,软件对各个设备的空间需求进行排序,并依次从Memory空间的起始地址开始分配,最终得到的设备地址空间分布如表7.4所示。
-
-
-表 7.4: 三个设备的地址空间分布
+
+
+表 7.4: 三个设备的地址空间分布
-
+设备号
名称
BAR号
大小
起始地址
结束地址
1
USB控制器
0
4KB
0x48015000
0x48015FFF
2
显示控制器
0
128MB
0x40000000
0x47FFFFFF
1
64KB
0x48000000
0x4800FFFF
3
网络控制器
0
4KB
0x48014000
0x48014FFF
1
16KB
0x48010000
0x48013FFF
+
+
+
经过这样的设备探测和驱动加载过程,可以将键盘、显卡、硬盘或者网卡等设备驱动起来,在这些设备上加载预存的操作系统,就完成了整个系统的正常启动。
如果把CPU比作一个大房间,至此,房间内灯火通明,门窗均已打开,门窗外四通八达。CPU及相关硬件处于就绪状态。
diff --git a/计算机总线接口技术.html b/计算机总线接口技术.html
index 559d9eb..8fd4192 100644
--- a/计算机总线接口技术.html
+++ b/计算机总线接口技术.html
@@ -216,8 +216,9 @@ div.csl-indent {
- 2.5 RISC指令集比较
- 2.6 C语言的机器表示
@@ -290,6 +291,7 @@ div.csl-indent {
- 5.3.2 CPU-北桥-南桥三片结构
- 5.3.3 CPU-弱北桥-南桥三片结构
- 5.3.4 CPU-南桥两片结构
+- 5.3.5 SoC单片结构
- 5.4 处理器和IO设备间的通信
@@ -423,16 +425,17 @@ div.csl-indent {
- 11.2.3 Cache一致性协议
- 11.3 多核处理器的互连结构
-- 11.4 典型多核处理器
+
- 11.4 多核处理器的同步机制
+- 11.5 典型多核处理器
-- 11.5 本章小结
-- 11.6 习题
+- 11.6 本章小结
+- 11.7 习题
- VI 系统性能评价与分析
- 12 计算机系统性能评价与性能分析
@@ -516,19 +519,71 @@ div.csl-indent {
6.3 片上总线
片上总线是指芯片片内互连使用的总线。芯片在设计时,常常要分为多个功能模块,这些功能模块之间的连接即采用片上互连总线。例如,一个高性能通用处理器在设计时,常常会划分为处理器核、共享高速缓存、内存控制器等多个模块,而一个SoC(System on Chip,片上系统)芯片所包含的模块就更多了。图6.1是一个嵌入式SoC芯片的内部结构,可以看到里面包含了很多功能模块,这些模块之间的连接就需要用到片上互连总线。这些模块形成了IP(Intellectual Property),一家公司在设计芯片时常常需要集成其他公司的IP。这些IP的接口使用大家共同遵守的标准时,才能方便使用。因此,芯片的片上互连总线也形成了一定的标准。目前业界公开的主流片上互连总线是ARM公司的AMBA系列总线。
-
-
-图 6.1: 君正M200芯片的结构图
+
+
+
+图 6.1: 君正M200芯片的结构图
+
AMBA(Advanced Microcontroller Bus Architecture,高级微控制器总线架构)系列总线包括AXI、AHB、ASB、APB等总线。下面对AMBA总线的一些特点进行概括说明,这些总线的详细内容可以参阅相关总线协议。
1.AXI总线
AXI(Advanced eXtensible Interface,高级可扩展接口)总线是一种高性能、高带宽、低延迟的片上总线。它的地址/控制和数据总线是分离的,支持不对齐的数据传输,同时在突发传输中只需要发送首地址即可。它使用分离的读写数据通道并支持乱序访问。AXI是AMBA 3.0规范中引入的一个新的高性能协议,目标是满足超高性能和复杂的片上系统(SoC)的设计需求。
AXI总线主设备的主要信号定义如表6.1所示。可以看到,AXI总线主要分为5个独立的通道,分别为写请求、写数据、写响应、读请求、读响应。每个通道采用握手协议独立传输。
-
-
-表 6.1: AXI总线主要信号定义
+
+
+表 6.1: AXI总线主要信号定义
-
+引脚名称
方向
描述
AWID[n:0]
输出
写请求标识号
AWADDR[m:0]
输出
写请求地址
AWSIZE[2:0]
输出
写请求数据宽度
AWLEN[3:0]
输出
写请求数据长度
AWBURST[1:0]
输出
写请求类型
AWAVALID
输出
写请求有效信号
AWREADY
输入
写请求接收准备好信号
WID[n:0]
输出
写数据标识号,与写请求标识号对应
WDATA[j:0]
输出
写数据
WSTRB[k:0]
输出
写数据屏蔽信号,1位对应8个数据位
WVALID
输出
写数据有效信号
WREADY
输入
写数据接收准备好信号
BID[n:0]
输入
写响应标识号,与写请求标识号对应
BRESP[1:0]
输入
写响应状态
WVALID
输入
写响应有效信号
WREADY
输出
写响应接收准备好信号
ARID[n:0]
输出
读请求标识号
ARADDR[m:0]
输出
读请求地址
ARSIZE[2:0]
输出
读请求数据宽度
ARLEN[3:0]
输出
读请求数据长度
ARBURST[1:0]
输出
读请求类型
ARAVALID
输出
读请求有效信号
ARREADY
输入
读请求接收准备好信号
RID[n:0]
输入
读数据标识号,与读请求标识号对应
RDATA[j:0]
输入
读数据
RRESP[1:0]
输入
读响应状态
RVALID
输入
读数据有效信号
RREADY
输出
写读数据接收准备好信号
+
+
+
AXI协议包括以下特点:
1)单向通道体系结构。信息流只以单方向传输,符合片内设计的要求。
2)支持多项数据交换。AXI协议支持的数据宽度很宽,最大可到1024位。通过并行执行突发(Burst)操作,极大地提高了数据吞吐能力,可在更短的时间内完成任务。
@@ -536,38 +591,53 @@ div.csl-indent {
(1) AXI架构
AXI协议是一个主从协议,每套总线的主设备和从设备是固定好的。只有主设备才可以发起读写命令。一套主从总线包含五个通道:写地址通道、写数据通道、写响应通道、读地址通道、读返回通道。读/写地址通道用来传送读写目标地址、数据宽度、传输长度和其他控制信息。写数据通道用来由主设备向从设备传送写数据,AXI支持带掩码的写操作,可以指定有效数据所在的字节。写响应通道用来传送写完成信息。读返回通道用来传送从设备读出的数据以及响应信息。
AXI协议的一次完整读写过程称为一个总线事务(Transaction),传输一个周期的数据称为一次传输(Transfer)。AXI协议允许地址控制信息在数据传输之前发生,并且支持多个并发访问同时进行,它还允许读写事务的乱序完成。图6.2和6.3分别说明了读写事务是如何通过读写通道进行的。
-
-
-图 6.2: 读事务架构
+
+
+
+图 6.2: 读事务架构
+
-
-
-图 6.3: 写事务架构
+
+
+
+图 6.3: 写事务架构
+
AXI使用双向握手协议,每次传输都需要主从双方给出确认信号。数据的来源方设置有效(Valid)信号,数据的接收方设置准备好(Ready)信号。只有当有效信号和准备好信号同时有效时,数据才会传输。读请求通道和写数据通道还各包含一个结束(Last)信号来指示一次突发传输的最后一个传输周期。
(2)互连架构
在一个使用AXI总线的处理器系统中,一般都会包含多个主设备和从设备。这些设备之间使用互连总线进行连接,如图6.4所示。在该互连结构中,任意一个主设备都可以访问所有的从设备。比如,主设备2可以访问从设备1、2、3、4。
-
-
-图 6.4: AXI设备的接口和互连
+
+
+
+图 6.4: AXI设备的接口和互连
+
为了减少互连结构的信号线个数,AXI的互连结构可以共享地址和数据通道,或者共享地址通道但使用多个数据通道。当需要连接的主从设备个数较多时,为了减少互连结构的信号线个数,AXI协议还可以很方便地支持多层次的互连结构。
(3)高频设计
AXI协议的每个传输通道都只是单向的信息传递,并且AXI协议对多个通道之间的数据传输没有规定特定的顺序关系,多个通道之间没有同步关系。因此,设计者可以很容易地在通道中插入寄存器缓冲,这对于高频设计是很重要的。
(4)基本事务
下面简要介绍AXI的读写事务。AXI协议的主要特点是使用VALID和READY握手机制进行传输。地址和数据信息都只在VALID和READY信号同时为高的情况下才进行传输。
-
-
-图 6.5: 突发读事务
+
+
+
+图 6.5: 突发读事务
+
图6.5显示了一个突发读事务的传输,其中请求由主设备发往从设备,响应由从设备发往主设备。地址信息在T2传输后,主设备从T4时刻开始给出读数据READY信号,从设备保持读数据VALID信号为低,直到读数据准备好后,才在T6时刻将读数据VALID信号拉高,主设备在T6时刻接收读数据。当所有读数据传输完成后,在T13时刻,从设备将RLAST信号拉高表示该周期是最后一个数据传输。
-
-
-图 6.6: 重叠的读事务
+
+
+
+图 6.6: 重叠的读事务
+
图6.6显示了一个重叠的读事务。在T4时刻,事务A的读数据还没有完成传输,从设备就已经接收了读事务B的地址信息。重叠事务使得从设备可以在前面的数据没有传输完成时就开始处理后面的事务,从而降低后面事务的完成时间。AXI总线上,通过ID对不同的事务加以区别。同一个读事务的请求与响应中,ARID与RID相同;同一个写事务的请求与响应中,AWID、WID与BID相同。
-
-图6.7是一个写事务的示例。主从设备在T2时刻传输写地址信息,接着主设备将写数据发送给从设备,在T9时刻,所有的写数据传输完毕,从设备在T10时刻给出写完成响应信号。
+
+
+
+图 6.7: 写事务
+
+
+图6.7是一个写事务的示例。主从设备在T2时刻传输写地址信息,接着主设备将写数据发送给从设备,在T9时刻,所有的写数据传输完毕,从设备在T10时刻给出写完成响应信号。
(5)读写事务顺序
AXI协议支持读写事务乱序完成。每一个读写事务都有一个ID标签,该标签通过AXI信号的ID域进行传输。同ID的读事务或者同ID的写事务必须按照接收的顺序按序完成,不同ID的事务可以乱序完成。以图6.6为例,图中事务A的请求发生在事务B的请求之前,从设备响应时事务A的数据同样发生在事务B的数据之前,这就是顺序完成。如果事务A与事务B使用了不同的ID,那么从设备就可以先返回事务B的数据再返回事务A的数据。
(6)AXI协议的其他特点
@@ -586,13 +656,17 @@ div.csl-indent {
ASB(Advanced System Bus)是第一代AMBA系统总线,同AHB相比,它支持的数据宽度要小一些,典型数据宽度为8位、16位、32位。它的主要特征有:流水线方式,数据突发传送,多总线主设备,内部有三态实现。
APB(Advanced Peripheral Bus)是本地二级总线(Local Secondary Bus),通过桥和AHB/ASB相连。它主要是为了满足不需要高性能流水线接口或不需要高带宽接口的设备间的互连。其主要优点是接口简单、易实现。
基于AMBA总线的计算机系统的结构如图6.8和图6.9所示。
-
-
-图 6.8: 使用AHB和APB连接的微控制器系统
+
+
+
+图 6.8: 使用AHB和APB连接的微控制器系统
+
-
-
-图 6.9: 使用AXI总线互连的通用高性能处理器
+
+
+
+图 6.9: 使用AXI总线互连的通用高性能处理器
+
片上互连总线的最大特点是高并行性。由于片内走线的距离短,线宽细,因此可以实现高并行性。片上互连总线的设计需要考虑总线的通用性、可扩展性、性能以及总线接口逻辑的设计简单性等方面。
@@ -601,52 +675,118 @@ div.csl-indent {
内存总线用于连接处理器和主存储器。
前面章节我们介绍了目前使用的主存储器——DRAM芯片,以及内存条、内存控制器的一些概念。内存控制器和内存芯片(或者说内存条)的接口就是内存总线。内存总线规范是由JEDEC(Joint Electron Device Engineering Council)组织制定的,它包含了一般总线的三个层级:机械层、电气层和协议层。
在机械层,JEDEC规定了内存芯片的封装方式、封装大小和引脚排布,内存条生产厂家可以据此设计内存条PCB板,可以使用不同DRAM厂家的芯片。同时,JEDEC也制定了内存条和计算机主板连接的规范,也就是内存插槽规范,规定了内存条的引脚个数、排布和内存条的长度、厚度、机械形式。这样不同厂家的内存条就可以在同一块主板上使用。图6.10是台式机使用的DDR3内存条和对应的内存插槽的图片。DDR3内存条使用双列直插式设计,每列分布了120个引脚,共240个引脚。中间的缺口不是位于内存条的正中心,目的是为了防止将内存条插反。图6.11是台式机使用的DDR2内存条的图片。DDR3内存条和DDR2内存条的长度相同,但内存条上的缺口位置是不同的,可以防止DDR2和DDR3内存条之间误插。
-
-
-图 6.10: 台式机的DDR3内存条和内存插槽
+
+
+
+图 6.10: 台式机的DDR3内存条和内存插槽
+
-
-
-图 6.11: 台式机的DDR2内存条
+
+
+
+图 6.11: 台式机的DDR2内存条
+
在电气层,JEDEC组织规定了DRAM芯片的电气特性。例如,DDR2内存使用1.8V电压,而DDR3内存使用1.5V电压。另外,规范还规定输入电压高低电平的标准、信号斜率、时钟抖动的范围等信号电气特性。
在协议层,JEDEC组织规定了DRAM芯片的操作时序。协议规定了DRAM芯片的上电和初始化过程、DRAM工作的几种状态、状态之间的转换,以及低功耗控制等内容。比如,DRAM初始化完成后,进入空闲态,通过激活(Activate)命令进入“打开一行”的激活态,只有在激活态,才可以读写DRAM的数据,单纯的读写操作后,DRAM仍会处于激活态,等待下一次读写。如果想要读写其他行,需要首先发送预充(Precharge)命令将DRAM转回空闲态,然后再发送激活命令。这些命令不是在任意时刻都可以发送的,需要满足协议规定的时序要求。图6.12给出了DDR2内存的状态转换图。
-
-
-图 6.12: DDR2内存各状态转换图
+
+
+
+图 6.12: DDR2内存各状态转换图
+
-
-
-表 6.2: 双面DDR3 UDIMM内存条的接口信号列表
+
+
+表 6.2: 双面DDR3 UDIMM内存条的接口信号列表
-
+引脚名称
描述
引脚名称
描述
A0-A15
SDRAM地址线
SCL
EEPROM I2C总线时钟
BA0-BA3
SDRAM bank地址
SDA
EEPROM I2C总线数据线
RAS#
SDRAM行地址选通
SA0-SA2
EEPROM I2C从设备地址
CAS#
SDRAM列地址选通
VDD
SDRAMCore电源
WE#
SDRAM写使能
VDDQ
SDRAM IO输出电源
S0#-S1#
SDRAM片选信号
VrefDQ
SDRAM IO参考电源
CKE0-CKE1
SDRAM时钟使能信号
VrefCA
SDRAM命令地址参考电源
ODT0-ODT1
SDRAM终端匹配电阻控制信号
Vss
电源地信号
DQ0-DQ63
DIMM内存数据线
VDDSPD
EEPROM电源
CB0-CB7
DIMM ECC数据线
NC
空闲引脚
DQS0-DQS8
SDRAM数据选通线
TEST
测试引脚
(差分对的正沿)
DQS0-DQS8
SDRAM数据选通线
RESET#
复位引脚
(差分对的负沿)
DM0-DM8
SDRAM数据Mask线
EVENT#
温度传感器引脚(可选)
CK0-CK8
SDRAM时钟信号线
VTT
SDRAM IO终端匹配电阻电源
(差分对的正沿)
CK0-CK8
SDRAM时钟信号线
RSVD
保留
(差分对的负沿)
+
+
+
DDR3内存条的接口信号见表6.2。内存条将多个DDR3 SDRAM存储芯片简单地并列在一起,因此表中所列的信号主要是DDR3 SDRAM的信号。此外,表中还包含了一组I2C总线信号(SCL、SDA)和I2C地址信号(SA0~SA2)用来支持内存条的软件识别。内存条将自身的一些设计信息(包括SDRAM类型、SDRAM的速度等级、数据宽度、容量以及机械尺寸标准等信息)保存在一个EEPROM中,该EEPROM可以通过I2C总线访问,称为SPD(Serial Present Detect)。计算机系统可以通过I2C总线来读取内存条的信息,从而自动匹配合适的控制参数并获取正确的系统内存容量。组装电脑时,用户可以选用不同容量、品牌的内存条而无须修改软件或主板,就离不开SPD的作用。值得一提的是,表中给出的信号是按照双面内存条带ECC功能列出来的,如果只有单面,或者不带ECC校验功能,只需将相应的引脚位置悬空。
DRAM存储单元是按照Bank、行、列来组织的,因此对DRAM的寻址是通过bank地址、行地址和列地址来进行的。此外,计算机系统中可以将多组DRAM串接在一起,不同组之间通过片选(CS)信号来区分。在计算机系统中,程序的地址空间是线性的,处理器发出的内存访问地址也是线性的,由内存控制器负责将地址转换为对应于DRAM的片选、Bank地址、行地址、列地址。
DDR3 SDRAM读操作时序如图6.13所示。图中命令(Command,简称CMD)由RAS_n、CAS_n和WE_n三个信号组成。当RAS_n为高电平,CAS_n为低电平,WE_n为高电平时,表示一个读命令。该图中,列地址信号延迟 (CL) 等于5个时钟周期,读延迟 (RL)等于5个时钟周期,突发长度(Burst Length,BL)等于8。控制器发出读命令后,经过5个时钟周期,SDRAM开始驱动DQS和DQ总线输出数据。DQ数据信号和DQS信号是边沿对齐的。在DQS的起始、DQ传输数据之前,DQS信号会有一个时钟周期长度的低电平,称为读前导(Read Preamble)。读前导的作用是给内存控制器提供一个缓冲时间,以开启一个信号采样窗口,将有用的读数据采集到内部寄存器,同时又不会采集到数据线上的噪声数据。
-
-
-图 6.13: DDR3 SDRAM读时序
+
+
+
+图 6.13: DDR3 SDRAM读时序
+
-
-
-图 6.14: DDR3 SDRAM写时序
+
+
+
+图 6.14: DDR3 SDRAM写时序
+
DDR3 SDRAM写操作的协议如图6.14所示。当RAS_n为高电平,CAS_n为低电平,WE_n为低电平时,表示一个写操作。读写操作命令的区别是WE_n信号的电平不同,读操作时该信号为高,写操作时该信号为低。写操作使用额外的数据掩码(Data Mask,DM)信号来标识数据是否有效。当DM为高时,对应时钟沿的数据并不写入SDRAM,当DM为低时,对应时钟沿的数据才写入SDRAM。DM信号与DQ信号同步。在写操作时,DQS信号和DQ信号是由内存控制器驱动的。同样,在DQS的起始、DQ传输数据之前,DQS信号也存在一个写前导(Write Preamble)。DDR3 SDRAM的写前导为一个周期的时钟信号,DDR2 SDRAM的写前导为半个时钟周期的低电平信号。
前面讲过SDRAM的基本操作包括激活(Activate)、读写(Read/Write)和预充电(Precharge)。当SDRAM接收到一个操作后,它需要在固定的时钟周期之后开始进行相应的动作,并且这些动作是需要经过一定的时间才能完成的。因此,对DRAM不同操作命令之间是有时间限制的。例如,对于DDR3-1600内存来说,当软件访问的两个地址正好位于内存的同一个Bank的不同行时,内存控制器需要首先针对第一个访问地址发出激活操作,经过13.75ns的时间,才可以发出读写操作。如果第一个访问是读操作,则需要经过至少7.5ns(此外还需满足tRASmin的要求,这里进行简化说明)的时间才可以发送预充电操作。预充电操作发送后,需要经过13.75ns的时间才可以针对第二个访问的行地址发送新的激活操作,然后经过13.75ns的时间,发送读写操作。因此,对SDRAM的同一个Bank的不同行进行读写存在较大的访问延迟。为了掩盖访问延迟,SDRAM允许针对不同Bank的操作并发执行。上述访问过程如图6.15所示。
-
-
-图 6.15: SDRAM的访问时序图
+
+
+
+图 6.15: SDRAM的访问时序图
+
提高内存总线访问效率的两个主要手段是充分利用行缓冲局部性和Bank级并行度。行缓冲局部性是说,当两个访存命令命中SDRAM的同一行时,两个命令可以连续快速发送;Bank级并行度是说,针对SDRAM的不同Bank的操作可以并发执行,从而降低后一个操作的访存延迟。下面以一个简单的例子来说明对SDRAM的不同访问序列的延迟的差别。
假定处理器发出了三个访存读命令,地址分别命中SDRAM的第0个Bank的第0行(列地址为0)、第0个Bank的第1行和第0个Bank的第0行(与第一个命令的列地址不同,假定列地址为1)。如果我们不改变访问的顺序,直接将这三个命令转换为对应SDRAM的操作发送给内存。则需要的时间如图6.16所示。图中,<B0,R0>表示第0个Bank的第0行,<B0,R1>表示第0个Bank的第1行。每一个读命令都会转换出对应于SDRAM的<激活,读数据,预充电>序列。假定使用的是DDR2-800E规格的内存,它对应的时序参数为:tRCD=15ns,tRP=15ns,tRASmin=45ns,tRC=60ns,tRL=15ns,tRTP=7.5ns,tCCD=10ns(4个时钟周期)。则读数据分别在第30ns(tRCD+tRL)、90ns(tRC+tRCD+tRL)和150ns(tRC+tRC+tRCD+tRL)返回给处理器。
假定我们改变命令发给内存的顺序,我们将第3个命令放到第1个命令之后发送,将第2个命令最后发送,则得到的访存序列如图6.17所示。在该图中,针对第0个Bank第0行第1列的命令不需要发送预充电和激活操作,而是在针对第0个Bank第0行第0列的命令之后直接发送。则处理器得到读数据的时间变为第30ns、第40ns和第90ns。相比上一种访存序列,第3个访存命令的读数据的访存延迟降低了110ns(40ns相比于150ns)。
-
-
-图 6.16: 调度前的命令序列
+
+
+
+图 6.16: 调度前的命令序列
+
-
-
-图 6.17: 调度后的命令序列
+
+
+
+图 6.17: 调度后的命令序列
+
对内存总线的控制是由内存控制器实现的。内存控制器负责管理内存条的初始化、读写、低功耗控制等操作。内存控制器接收处理器发出的读写命令,将其转化为内存芯片可以识别的DRAM操作,并负责处理时序相关问题,最终返回数据(对于读命令)或者返回一个响应(对于写命令)给处理器。内存控制器一般还包括命令调度功能,以提高内存总线的访问效率。对于处理器来说,它只需要发送读写命令给内存控制器就可以了,而不必关心内存的状态以及内存是如何被读写的。
@@ -658,33 +798,96 @@ div.csl-indent {
6.5.1 HyperTransport总线
HyperTransport总线(简称HT总线)是AMD公司提出的一种高速系统总线,用于连接微处理器与配套桥片,以及多个处理器之间的互连。HT总线提出后,先后发展了HT1.0、HT2.0、HT3.0等几代标准,目前最新的标准为HT3.1。
-图6.18是采用HT总线连接处理器与桥片的结构示意图。
-
-与并行总线不同的是,串行总线通常采用点对点传输形式,体现在计算机体系结构上,就是一组串行总线只能连接两个芯片。以龙芯3A2000/3A3000为例,在四路互连系统中,一共采用了7组HT互连总线,其中6组用于四个处理器间的全相联连接,1组用于处理器与桥片的连接,如图6.19所示。而作为对比,PCI总线则可以在同一组信号上连接多个不同的设备,如图6.20所示。
-
-
-图 6.19: 龙芯3A2000/3A3000四路系统结构示意图
+图6.18是采用HT总线连接处理器与桥片的结构示意图。
+
+
+
+图 6.18: CPU-南桥两片结构
+
-
-
-图 6.20: PCI总线设备连接
+与并行总线不同的是,串行总线通常采用点对点传输形式,体现在计算机体系结构上,就是一组串行总线只能连接两个芯片。以龙芯3A2000/3A3000为例,在四路互连系统中,一共采用了7组HT互连总线,其中6组用于四个处理器间的全相联连接,1组用于处理器与桥片的连接,如图6.19所示。而作为对比,PCI总线则可以在同一组信号上连接多个不同的设备,如图6.20所示。
+
+
+
+图 6.19: 龙芯3A2000/3A3000四路系统结构示意图
+
+
+
+
+
+图 6.20: PCI总线设备连接
+
HT总线的软件架构与PCI总线协议基本相同,都采用配置空间、IO空间和Memory空间的划分,通过对配置寄存器的设置,采用正向译码的方向对设备空间进行访问。基于PCI总线设计的设备驱动程序能够直接使用在HT总线的设备上。
但在电气特性及信号定义上,HT总线与PCI总线却大相径庭,HT由两组定义类似但方向不同的信号组成。其主要信号定义如表6.3所示。
-
-
-表 6.3: HT总线主要信号定义
+
+
+表 6.3: HT总线主要信号定义
-
-
-
-图 6.21: HT总线连接
+引脚名称
方向
描述
TX_CLKp/TX_CLKn
输出
发送端时钟信号
TX_CTLp/TX_CTLn
输出
发送端控制信号,用于区分命令包与数据包
TX_CADp[n:0]/TX_CADn[n:0]
输出
发送端命令地址数据复用信号,用于传输各种包
RX_CLKp/RX_CLKn
输入
接收端时钟
RX_CTLp/RX_CTLn
输入
接收端控制信号,用于区分命令包与数据包
RX_CADp[n:0]/RX_CADn[n:0]
输入
接收端命令地址数据复用信号,用于传输各种包
+
+
+
+
+
+
+图 6.21: HT总线连接
+
可以看到,图6.21中两个芯片通过定义相同的信号进行相互传输。与上一节介绍的DDR内存总线所不同的是,HT总线上,用于数据传输的信号并非双向信号,而是由两组方向相反的单向信号各自传输。这种传输方式即通常所说的全双工传输。发送和接收两个方向的传输可以同时进行,互不干扰。而采用双向信号的总线,例如DDR内存总线或者PCI总线,只能进行半双工传输,其发送和接收不能同时进行。而且在较高频率下,发送和接收两种模式需要进行切换时,为了保证其数据传输的完整性,还需要在切换过程中增加专门的空闲周期,这样更加影响了总线传输效率。
PCI接口信号定义如图6.22所示。PCI总线上使用起始信号(FRAME#)及相应的准备好信号(TRDY#、IRDY#)、停止信号(STOP#)来进行总线的握手,控制总线传输。与PCI总线不同,HT总线信号定义看起来非常简单,没有类似PCI总线的握手信号。
-
-
-图 6.22: PCI总线信号定义
+
+
+
+图 6.22: PCI总线信号定义
+
实际上HT总线的读写请求是通过包的形式传输的,将预先定义好的读写包通过几个连续的时钟周期进行发送,再由接收端进行解析处理。同时,HT总线采用了流控机制替代了握手机制。
流控机制的原理并不复杂。简单来说,在总线初始化完成后,总线双方的发送端将自身的接收端能够接收的请求或响应数通过一种专用的流控包通知对方。总线双方各自维护一组计数器用于记录该信息。每需要发出请求或响应时,先检查对应的计数器是否为0。如果为0,表示另一方无法再接收这种请求或响应,发送方需要等待;如果不为0,则将对应的计数器值减1,再发出请求或响应。而接收端每处理完一个请求或响应后,会再通过流控包通知对方,对方根据这个信息来增加内部对应的计数器。正是通过这种方式,有效消除了总线上的握手,提升了总线传输的频率和效率。
@@ -697,61 +900,269 @@ div.csl-indent {
信息包的作用是为互连的两端传递底层信息,本身不需要流控。这意味着对于信息包,无论何时都是可以被接收并处理的。流控信息就是一种典型的信息包。信息包的格式如表6.4所示。
其中,“命令”域用于区分不同的包。对不同的命令,包的其他位置表示的内容之间有所不同。
HT也是采用DDR传输,即双倍数据率传输,在时钟的上升、下降沿各传一组数据。每种包大小都是4字节的倍数。图6.23是在总线上传输的时序示意图,以8位的CAD总线为为例。在CTL为高电平的时候,表示传输的是控制包,而CTL为低时,表示传输的是数据包。图中CAD信息上的数字对应包格式表中的具体拍数。
-
-
-表 6.4: HT信息包格式
+
+
+表 6.4: HT信息包格式
-
-
-
-图 6.23: HT总线传输示意图
+字节\数据位
7
6
5
4
3
2
1
0
0
命令相关内容
命令
1
命令相关内容
2
命令相关内容
3
命令相关内容
+
+
+
+
+
+
+图 6.23: HT总线传输示意图
+
表6.5为请求包的格式。因为需要传输地址信息,请求包最少需要8字节。当使用64位地址时,请求包可以扩展至12字节。大部分请求包地址的[7:2]是存放在第3拍。因为数据的最小单位为4字节,地址的[1:0]不需要进行传输。当传输的数据少于4字节时,利用数据包的屏蔽位进行处理。
-
-
-表 6.5: HT请求包格式
+
+
+表 6.5: HT请求包格式
-
+字节\数据位
7
6
5
4
3
2
1
0
0
顺序标识
命令
1
顺序标识
设备标识
2
命令相关内容
3
命令相关内容
4
地址[15:8]
5
地址[23:16]
6
地址[31:24]
7
地址[39:32]
+
+
+
请求包主要是读请求和写请求。其中读请求不需要数据,而写请求需要跟随数据包。
表6.6是响应包的格式。响应包大小为4字节。与请求包类似,写响应包不需要数据,而读响应包需要跟随数据包。
-
-
-表 6.6: HT响应包格式
+
+
+表 6.6: HT响应包格式
-
+字节\数据位
7
6
5
4
3
2
1
0
0
命令相关内容
命令
1
设备标识
2
错误
命令相关内容
3
错误
命令相关内容
+
+
+
6.5.3 设备总线
设备总线用于计算机系统中与IO设备的连接。
PCI(Peripheral Component Interconnect)总线是一种对计算机体系结构连接影响深远并广泛应用的设备总线。PCIE(PCI Express)可以被看作PCI总线的升级版本,兼容PCI软件架构。PCIE总线被广泛地用作连接设备的通用总线,在现有计算机系统中已经基本取代了PCI的位置。PCIE接口在系统中的位置如图6.24所示,一般与SATA、USB、显示等设备接口位于同样层次,用于扩展外部设备。
-
-
-图 6.24: PCIE接口位置示意图
+
+
+
+图 6.24: PCIE接口位置示意图
+
6.5.4 PCIE总线
与HT类似,PCIE总线也是串行总线。PCIE与设备进行连接的时候同样采用点对点的方式,一组PCIE接口只能连接一个设备。为了连接多个设备,就需要实现多个接口,如图6.25所示。
与HT又有所不同,两者在信号定义和接收发送方法上有很大差别。上一节介绍过,HT总线主要包括三种信号,分别为CLK、CTL、CAD,其中CLK作为随路时钟使用,用于传递总线的频率信息并用作数据恢复。
-
-
-图 6.25: PCIE接口连接示意图
+
+
+
+图 6.25: PCIE接口连接示意图
+
PCIE的总线信号如表6.7所示。
-
-
-表 6.7: PCIE总线主要信号定义
+
+
+表 6.7: PCIE总线主要信号定义
-
+引脚名称
方向
描述
TXp/TXn[n:0]
输出
发送信号
RXp/RXn[n:0]
输入
接收信号
+
+
+
可以看到,PCIE接口上只有用于数据传输的信号。HT接口上,CAD[n:0]通常是以8位为单位,共用一组时钟信号,总线宽度可以为8位、16位或32位。而PCIE接口上的各个TX信号之间相互独立,最小单位为1位,称之为通道(Lane)。常见的总线宽度有1位、4位、8位及16位。如千兆网卡、SATA扩展卡、USB扩展卡等总线宽度大多为1位,而显卡、RAID卡等总线宽度通常为16位。
PCIE在进行传输时,仅仅发送数据信号,而没有发送时钟信号。在接收端通过总线初始化时约定好的数据序列恢复出与发送端同步的时钟,并使用该时钟对接收到的数据信号进行采样,得到原始数据。
6.5.5 PCIE包格式
PCIE总线的传输同样以包(事务层包,Transaction Level Packet,简称TLP)为单位,其包格式如图6.26所示。PCIE包主要分为TLP首部与数据负载两部分,其作用与HT包类似,可以对应到HT包中的控制包与数据包。PCIE包同样是以4字节为单位增长。
-
-
-图 6.26: PCIE总线包格式
+
+
+
+图 6.26: PCIE总线包格式
+
对于具体包格式的定义,PCIE与HT各有不同。尤其是PCIE包在协议上最多可以一次传输4KB的数据,而HT包最多一次传输64字节。PCIE的具体包格式定义在此不再展开,感兴趣的读者可以参考PCIE相关协议。
此外,PCIE同样采用了流控机制来消除总线握手。
diff --git a/计算机系统性能评价与性能分析.html b/计算机系统性能评价与性能分析.html
index 9375b43..cebc7b9 100644
--- a/计算机系统性能评价与性能分析.html
+++ b/计算机系统性能评价与性能分析.html
@@ -216,8 +216,9 @@ div.csl-indent {
- 2.5 RISC指令集比较
- 2.6 C语言的机器表示
@@ -290,6 +291,7 @@ div.csl-indent {
- 5.3.2 CPU-北桥-南桥三片结构
- 5.3.3 CPU-弱北桥-南桥三片结构
- 5.3.4 CPU-南桥两片结构
+- 5.3.5 SoC单片结构
- 5.4 处理器和IO设备间的通信
@@ -423,16 +425,17 @@ div.csl-indent {
- 11.2.3 Cache一致性协议
- 11.3 多核处理器的互连结构
-- 11.4 典型多核处理器
+
- 11.4 多核处理器的同步机制
+- 11.5 典型多核处理器
-- 11.5 本章小结
-- 11.6 习题
+- 11.6 本章小结
+- 11.7 习题
- VI 系统性能评价与分析
- 12 计算机系统性能评价与性能分析
@@ -500,11 +503,61 @@ div.csl-indent {
12.1.1 计算机系统常用性能评价指标
计算机系统的性能有许多衡量指标,如执行时间或者响应时间、吞吐率、加速比、每条指令的时钟周期数(CPI)、每秒执行百万条指令数(MIPS)、每秒执行百万浮点运算数(MFLOPS)、每秒执行的事务数(TPS)和归一化的执行时间等。
我们通过一些实际的示例来看看不同的性能指标。Openbenchmarking.org网站收集了大量的开源测试程序集合,这个测试程序集合称为Phoronix Test Suite。表12.1给出了AMD Athlon II X4 645的测试结果。从表中可以看出性能衡量指标包括以下方面:完成任务的执行时间,例如并行的BZIP2压缩和LAME MP3编码,执行时间越短越好。每秒多少帧,每秒的帧数是越多越好,例如H.264视频编码和射击游戏《帕德曼的世界》(World of Padman)。MIPS,例如7-ZIP测试压缩速度。MFLOPS,如Himeno中泊松压力方程求解。每秒执行了多少个事务,如PostgreSQL pgbench测试TPC-B。每秒传递多少个签名,如OpenSSL中RSA测试。每秒服务多少个请求,如Apache网页服务器。)每秒执行的百万次操作数(Mop/s),如NPB中的EP.B。每秒完成计算多少个节点,如TSCP人工智能下棋程序,每秒能下多少步棋。带宽,即每秒能完成多少MB的访存操作,如STREAM测试程序。
-
-
-表 12.1: AMD Athlon处理器的Phoronix Test Suites测试结果
+
+
+表 12.1: AMD Athlon处理器的Phoronix Test Suites测试结果
-
+测试程序
指标
性能
分值
World of Padman v1.2
每秒帧数(FPS)
越大越好
177.33
H.264 v2015-11-02
每秒帧数(FPS)
越大越好
101.97
GraphicsMagic v1.3.12
HWB Color Space
每分钟迭代次数
越大越好
108.00
John The Ripper v1.7.9
Traditional DES
每秒破解的数目
越大越好
5,174,833.00
John The Ripper v1.7.9
Blowfish
每秒破解的数目
越大越好
1,970.00
TTSIOD 3D Renderer v2.2w
每秒帧数(FPS)
越大越好
39.01
Parallel BZIP2 Compression v1.0.5
秒数
越小越好
27.98
7-ZIP Compression v9.20.1
Compress Speed Test
MIPS
越大越好
7,242.00
LAME MP3 Encoding v3.99.3
WAV to MP#
秒数
越小越好
22.86
x264 v2011-12-06
H.264 Video Encoding
每秒帧数(FPS)
越大越好
54.04
FFmpeg v0.10
AVI to NTSC VCD
秒数
越小越好
17.57
OpenSSL v1.0.0e
RSA 4096-bit
每秒签名数
越大越好
58.58
Himeno Benchmark v3.0
Poisson pressure solver
MFLOPS
越大越好
516.47
PostgreSQL pgbench v8.4.11
TPC-B transaction per second
TPS
越大越好
427.13
Apache benchmark v2.2.21
Static web page serving
每秒请求数
越大越好
11,784.34
C-Ray v1.1
秒数
越小越好
120.23
POV-Ray v3.6.1
秒数
越小越好
1,129.00
Smallpt v1.0
Global Illumnination Renderer
秒数
越小越好
241.00
TSCP v1.81
AI Chess performance
每秒计算的节点数
越大越好
261,528.00
NAS Parallel benchmarks v3.3
EP.B
总的Mop/s
越大越好
70.06
STREAM v2009-04-11
Copy
MB/s
越大越好
6,381.28
+
+
+
归根到底,计算机的性能最本质的定义是“完成一个任务所需要的时间”。计算机系统完成某个任务所花费的时间,我们称为执行时间。时间最直接的定义是所谓墙上时钟时间、响应时间或者持续时间。计算机中完成一个任务的时间包括CPU计算、磁盘的访问、内存的访问、输入输出的活动和操作系统的开销等所有的时间。我们常说的CPU时间表示CPU计算的时间,而不包括等待IO的时间或者执行其他程序的时间。CPU时间能进一步被分为花在程序执行上的CPU时间(用户CPU时间)和花在操作系统上的执行时间(系统CPU时间)。在比较两台机器(X和Y)的性能时,X的速度是Y的n倍是指Y的执行时间是X的执行时间的n倍。 执行时间和性能成反比,所以下述关系成立:n = (Y的执行时间/X的执行时间) = (X的性能/Y的性能)。
历史上很长一段时间,测量计算机的速度通过时钟频率(MHz或GHz)来描述,这表示CPU时钟的每秒时钟周期数。每台计算机都有一个时钟,运行在一定的频率上,这些离散的时间事件称为时钟滴答和时钟周期,计算机的设计者把时钟周期时间表示为一个持续时间如1ns(对应的时钟频率为1GHz),通常称一个时钟周期为一拍(Cycle)。2000年时主频基本就是速度的标志,主频越高的芯片卖得价格也越高。然而这种方法会有一定的误导,因为一个有很高时钟频率的机器,不一定有很高的性能,其他影响速度的因素还包括功能单元的数量、总线的速度、内存的容量、程序动态执行时指令的类型和重排序特性等。因此,厂商已经不再把时钟频率当作测量性能的唯一指标。
对于处理器的性能评价,有相应的处理器性能公式。一个程序的CPU时间可以描述为:
@@ -537,11 +590,61 @@ CPI = \frac{\sum^{n}_{i=1}IC_i\times CPI_i}{Instruction\ count} = \sum^{n}_{i=1}
如何获得CPU性能公式中这些参数的值呢?可以通过运行程序来测量CPU的执行时间,例如通过Linux系统中运行“time ./app”可以获得程序app的执行时间。时钟周期就是频率的倒数,在不开启睿频和变频技术的情况下,CPU的频率是固定的。程序的执行指令数和CPI的测量可以通过体系结构模拟器来获得,或者通过处理器中硬件计数器来获得,例如通过Linux系统中的perf工具就可以获得程序的执行指令数目和CPI。
程序的执行指令数、CPI和时钟频率实际上和实现的算法、编程语言、编译器、指令系统结构等相关,表12.2列出了它们之间的关系以及它们影响了CPU性能公式中的哪些参数。
时钟周期基本由微体系结构设计、物理设计电路和工艺决定,CPI由微体系结构特性和指令系统结构决定,程序的执行指令数由指令系统结构和编译器技术决定。当前,也有研究工作进一步比较了RISC和CISC处理器,认为指令系统结构的影响越来越小,一是当前X86处理器在处理器内部把X86指令翻译为类RISC指令,二是现代的编译器更倾向于选择X86中简单的类RISC指令进行汇编,三是Cache技术的采用和流水线内部指令融合技术等使得指令系统结构的影响越来越小,处理器的最终性能还是决定于微体系结构的设计和电路的设计。
-
-
-表 12.2: 算法、编程语言、编译器、指令系统结构和CPU性能公式的关系
+
+
+表 12.2: 算法、编程语言、编译器、指令系统结构和CPU性能公式的关系
-
+硬件或软件
影响什么
如何影响
算法
程序的执行指令数
算法决定源程序执行指令的数目,好的算法可以大幅度减少运算的次数
编程语言
程序的执行指令数
编程语言可能对执行指令数产生巨大的影响,比如解释执行、即时编译或者原生编译的三类语言完成同样的功能所需要的指令数可能有数量级的差异
编译器和库
程序的执行指令数、CPI
编译器和库决定了源程序到计算机指令的翻译过程,编译程序的效率既影响到程序的执行指令数又影响到CPI,如Intel的ICC编译器编出来的程序,效率可比GCC高30%,其能充分利用向量化指令和针对处理器结构的优化
指令系统结构
程序的执行指令数、CPI和时钟频率
指令系统结构影响到CPU性能的3个方面,因为它影响到完成某个功能所需的指令数、每条指令的周期数,以及处理器的时钟频率
微体系结构
CPI和时钟频率
微体系结构的改进可以降低CPI,也可以细分流水线来提高频率
物理设计
时钟频率
物理设计和电路的进步可以降低每个时钟周期的FO4,从而提高时钟频率
工艺
时钟频率
工艺的进步使得晶体管变快从而提高时钟频率
+
+
+
上述介绍了计算机系统的性能评价指标,也说明了CPU性能公式与CPI、频率以及指令数目三个要素相关。可以使用这些性能指标来衡量机器的性能或者指导计算机系统的优化。
@@ -562,9 +665,11 @@ CPI = \frac{\sum^{n}_{i=1}IC_i\times CPI_i}{Instruction\ count} = \sum^{n}_{i=1}
12.2.1 微基准测试程序
在处理器的设计过程中,尤其是在微体系结构的设计空间探索的过程中,我们希望有一类小的测试程序,能在很短的时间内跑完,可在模拟器、RTL仿真模型或者FPGA平台上执行,覆盖微体系结构的特性,如分支预测器的精度、Cache的行为、流水线的效率、各项队列大小、重命名寄存器数目和功能部件数量的影响等,这就导致了微基准测试程序(下面有时简称微测试程序)的出现。在处理器性能测试和分析中,常用的微基准测试程序包括:Sim-alpha的microbench、bp_microbench、LMbench、STREAM、Coremark、Coremark-pro和Unixbench等。
微测试程序(microbench)是一系列很小的测试程序或者代码片段。得克萨斯州奥斯丁大学的R.Desikan和Doug Burger在设计Sim-alpha模拟器的时候,为了将Sim-alpha模拟器和真实处理器Alpha21264进行校准,设计了一系列的微测试程序。这些测试程序对处理器核的某个模块进行了测试,同时排除了处理器其他模块的影响,所以特别适合处理器设计时对某个模块的选择和优化,例如分支预测器模块、执行单元模块和访存流水线模块,这几个模块可比较准确地反映流水线的效率。下面的图12.1中给出了部分微测试程序,第一行用于测试指令流水线的前端(front-end),例如行预测器的实现和分支预测器的实现,C表示测试的控制流,第二行用于测试指令流水线的执行核心(execution core),例如调度和发射逻辑(scheduler),E表示执行核心,最后一行用于测试内存系统的参数,例如一级Cache的延迟、二级Cache的延迟和内存的延迟,M表示内存系统。微测试程序更为详细的描述见参考文献[34]。Sim-alpha的微测试程序的源代码可以从网站http://www.cs.utexas.edu/users/cart/code/microbench.tgz下载。
-
-
-图 12.1: 微基准测试程序集
+
+
+
+图 12.1: 微基准测试程序集
+
亨茨维尔阿拉巴马大学的A.Milenkovic给出了一系列和分支预测器相关的微基准测试程序集,用于测试微体系结构中和分支预测器相关的参数,例如跳转目标缓存(Branch Target Buffer,简称BTB)的组织、全局历史寄存器的位数和局部预测器每一项的位数等。这些微测试程序可以用于编译器的代码优化,也可以用于处理器设计中对于各种分支预测器组织的性能评估,其网站(www.ece.uah.edu/~lacasa/bp_mbs/bp_microbench.htm)上给出了相关的代码。
其中GenCode.c程序用于处理器中BTB结构参数的测试,基本思想是构造一个微测试程序,在一个loop循环中包含B个条件分支指令,每个条件分支指令如jle和jle之间(LoongArch为bne和bne之间)的距离为D,如果增加B的大小和D的大小,所有的条件分支指令都能被装载到BTB中,也就是说当B等于NBTB-1,其中NBTB为BTB的项数,则分值误预测率趋近于零,这样就可以判断处理器中BTB的相连度和项数。
@@ -876,17 +981,117 @@ void main(int argc, char **argv)
LMbench是由HP以色列海法实验室开发的一套微测试程序集,可以测量许多和系统性能相关的因素。LMbench采用ANSI C编码、POSIX接口,是个多平台开源基准测试程序,能够测试包括文件读写、内存操作、进程创建和销毁开销、网络等性能。一般来说LMbench衡量两个关键特征:延迟和带宽。带宽就是系统中搬运数据的能力,如各级Cache的带宽、内存的带宽、缓存的IO带宽等,测量的方式包括bcopy库、手工循环展开bcopy、直接从内存中读、非拷贝的写、管道、进程间通信和TCP套接字等,可以采用read和mmap的API接口。延迟是系统的反馈时间或者开销,如各级Cache的访问延迟、内存访问的延迟和操作系统中某一项功能的开销,包括信号处理的开销、进程创建的时间、上下文切换的时间、进程间通信的开销、文件系统的延迟和磁盘的延迟等。Sun公司在开发UltraSPARC和Intel 公司在开发Pentium Pro的过程中都曾经使用LMbench用于发现微处理器设计上的性能瓶颈,Linux操作系统在kernel开发的性能调优中也曾使用了这些工具。表给出了LMbench测试程序集的各种工具及测试说明。
-
-
-表 12.3: LMbench微测试程序集
+
+
+表 12.3: LMbench微测试程序集
-
+工具名称
测量
延迟
lat_connect
TCP连接
lat_ctx
通过基于管道的“hot-potato”令牌传递的上下文切换
lat_dram_page
DRAM页开启
lat_fcntl
fcntl文件锁“hot-potato”令牌传递
lat_fifo
FIFO“hot-potato”令牌传递
lat_fs
文件创建和删除
lat_http
http GET请求延迟
lat_mem_rd
内存读的延迟
lat_mmap
mmap操作
lat_ops
对于基本数据类型(int, int64, float, double)的基本操作(xor, add, mul, div, mod)延迟
lat_pagefault
缺页处理
lat_pipe
pipe“hot-potato”令牌传递
lat_pmake
完成N个并行任务的时间
lat_proc
过程调用的开销以及使用fork, fork和execve,fork和sh进程创建进程的时间
lat_rand
随机数产生器
lat_rpc
SUN RPC远程过程调用
lat_select
select操作
lat_sem
信号量“hot-potato”令牌传递
lat_sig
信号处理的安装和处理
lat_syscall
open、close、getppid、write、stat、fstat系统调用
lat_tcp
TCP“hot-potato”令牌传递
lat_udp
UDP“hot-potato”令牌传递
lat_unix
UNIX“hot-potato”令牌传递
lat_unix_connect
UNXI socket连接
lat_usleep
usleep、select、pselect、nanosleep、settimer时间分辨率
带宽
bw_file_rd
文件的读带宽
bw_mem
内存的读带宽、写带宽和拷贝带宽
bw_mmap_rd
从mmap的内存中读取带宽
bw_pipe
pipe,进程间数据拷贝
bw_tcp
TCP进程间数据拷贝
bw_unix
UNIX进程通信带宽
其他
disk
磁盘的带宽和寻道时间
line
Cache行的大小
lmdd
dd程序
mhz
CPU时钟频率
par_mem
内存子系统的并发性(ILP)
par_ops
基本操作的并发性(ILP)
stream
STREAM
tlb
TLB大小
+
+
+
STREAM基准测试程序测量计算机的可持续内存带宽。STREAM是一个简单的合成测试程序,主要是测量内存的带宽(MB/s)和简单向量核心代码的计算速率。它由John McCalpin在Delaware大学执教的时候开发,并在随后成为工业界的标准测试程序,其有Fortran和C的代码,也有单处理器和多线程的版本,多线程版本包括OpenMP和MPI的并行。如表12.4所示,STREAM测试程序由Copy 、Scale 、Add 和 Triad四部分组成,测试了4个循环,并给出不同的内存带宽峰值。STREAM的基本原则是每个数组必须不小于最后一级Cache(LLC)大小的四倍或者100万个元素(取两者中较大的那个)。通常内存带宽比单线程STREAM测出来的值要高,为了能达到内存的饱和带宽,可以采用两种方法:一种是吞吐量的方法,执行多个stream的实例;另一种是执行OpenMP的版本。基于标准的STREAM测试程序,可以衍生出其他测试内存带宽的程序,改变数组访问跳步stride=1的简单循环,采用stride=N的操作(N=2,3,4,5,6,7,8,9,10,20,40),采用反向的(stride=-1)循环以及基于索引的操作,包括load采用索引的数组和store采用索引的数组。
-
-
-表 12.4: STREAM基准测试程序
+
+
+表 12.4: STREAM基准测试程序
-
+Copy
a(i)=b(i)
2个双精度浮点的访存操作(16字节),每个迭代没有浮点操作
Scale
a(i)=q*b(i)
2个双精度浮点的访存操作(16字节),每个迭代包含一个浮点乘法操作
Add
a(i)=b(i)+c(i)
3个双精度浮点的访存操作(24字节),每次迭代包含一个浮点加法操作
Triad
a(i)=b(i)+q*c(i)
3个双精度浮点的访存操作(24字节),每次迭代两个浮点操作
+
+
+
/* stream.c */
#ifndef STREAM_ARRAY_SIZE
@@ -968,31 +1173,231 @@ void main(int argc, char **argv)
CoreMark是一个综合性的基准测试程序,主要用于测量嵌入式系统中CPU的性能。2009年由嵌入式微处理器基准测试协会(英文简称EEMBC)开发,用于取代过时的Dhrystone测试程序。代码使用C语言编写,主要执行:列表操作(列表的插入和删除、反转列表和排序等),矩阵运算(矩阵加、矩阵常量乘、矩阵向量乘、矩阵和矩阵的乘),简单状态机(扫描字符串进行状态转换,主要测试switch-case和if语句的行为),CRC运算(测试循环冗余校验运算和用于测试过程的自检)。CoreMark程序有很多优点,如代码量很小、可移植性高、很容易理解、免费以及测试结果为单一的分值。CoreMark避免了Dhrystone所存在的一些问题,首先,CoreMark程序中运算所需要的值不会在编译的时候产生,这样就能确保编译器不会在编译的时候预计算结果,从而减少了编译器优化选项的干扰,其次,CoreMark没有调用库函数,第三,测试结果为单一分值,每秒钟执行了多少次迭代,便于不同处理器之间分值的比较。
CoreMark-Pro是EEMBC组织推出的并行基准测试程序,用于处理器流水线、存储子系统和多核等综合性能测试,其包含了5类定点和4类浮点应用,如JPEG压缩、ZIP压缩、XML解析、SHA-256算法和FFT、求解线性代数和神经网络算法等。
UnixBench 是一款测试类Unix系统基本性能的工具,主要测试项目如表12.5所示。其中,Dhrystone测试的核心为字符串处理,Whetstone 测试用于测试浮点运算效率和速度。这些测试规模比较小容易受到编译器、代码优化、系统库以及操作系统的影响而产生波动。
-
-
-表 12.5: UnixBench测试项目
+
+
+表 12.5: UnixBench测试项目
-
+测试项目
项目描述
Dhystone
测试和比较定点计算性能
Whetstone
测试和比较浮点计算性能
Execl系统调用
测量每秒能执行的execl系统调用的次数
文件拷贝
测量数据从一个文件拷贝到另一个文件的速率
管道吞吐率
测量一个进程每秒能执行的把512字节写入管道再读回来的次数
基于管道的上下文切换
测量两个进程基于管道交换一个不断增长的整数的速度
进程创建
测量一个进程创建和回收一个立刻退出的子进程的速度
Shell脚本
测量进程每分钟能执行的一些文件操作脚本的次数
系统调用开销
测量进入和退出操作系统内核的开销
图形测试
粗略测量系统2D和3D图形操作的性能
+
+
+
12.2.2 SPEC CPU基准测试程序
基准测试程序中最重要的一类就是SPEC组织推出的SPEC CPU系列测试程序集。SPEC是由计算机厂商、系统集成商、大学、研究机构、咨询公司等多家单位组成的非营利组织,这个组织的目标是建立、维护一套用于评估计算机系统的标准。SPEC组织创建了SPEC CPU系列测试程序集合,主要关注CPU的性能,如SPEC CPU89、SPEC CPU92、SPEC CPU95、SPEC CPU2000、SPEC CPU2006和SPEC CPU2017。SPEC基准测试程序来自于真实的程序,做了适当的修改,主要是为了可移植性和减少IO影响。SPEC测试代表了绝大多CPU密集型的运算,包括编程语言、压缩、人工智能、基因序列搜索、视频压缩及各种力学的计算等,包含了多种科学计算,可以用来衡量系统执行这些任务的快慢。SPEC CPU测试中,测试系统的处理器和编译器都会影响最终的测试性能,而磁盘、网络等IO和图形子系统对于SPEC CPU的影响比较小。SPEC CPU基准测试程序特别适用于桌面系统和单CPU服务器系统的CPU性能测试。
以下图表分别是SPEC CPU2000、2006和2017具体的测试项目和说明。
-
-
-表 12.6: SPEC CPU2000程序及描述
+
+
+表 12.6: SPEC CPU2000程序及描述
-
-
-
-表 12.7: SPEC CPU2006程序及描述
+ 定点测试程序
语言
分类
描述
164.gzip
C
压缩
gzip来源于是GNU的gzip。gzip使用LZ77(Lempel-Ziv)压缩算法,所有的压缩和解压缩都在内存中发生
175.vpr
C
FPGA电路布局和布线
FPGA集成电路中的布局和布线程序,能自动实现映射技术的电路,如网表或超图,把FPGA逻辑块和IO pad连在一起
176.gcc
C
C编程语言编译器
基于gcc版本2.7.2,它产生Motorola 88200处理器的代码,该编译过程开启了许多优化的选项
181.mcf
C
组合,优化
该程序为解决公共交通公司单车场容量约束车辆调度问题设计求解的算法
186.crafty
C
棋类游戏,国际象棋
高性能计算机棋类程序,包含大量的逻辑操作,例如与、或、异或和移位。其输入5个不同棋盘和深度,搜索树决定下一步的移动,用于比较处理器的定点、分支预测和流水线的效率
197.parser
C
字处理
基于link语法的英文语法解析器,解析器包含一个60000词的字典,输入为一个句子,输出为句子的分析,分析捕获了句子的语法结构
252.eon
C++
计算机可视化技术
一种基于概率的射线追踪器。其渲染了一个150x150像素屋子角落前方的椅子,渲染的算法采用Kajiya、Cook和Rushmeier
253.perlbmk
C
PERL编程语言
解释执行语言Perl,包括四个脚本:Email到HTML的转换MHonArc;Specdiff脚本;使用标准迭代算法寻找完全数;检测所产生的1000个随机数
254.gap
C
群论,解释器
GAP表示群论、算法和编程,其实现了计算群论中的一个语言和库。程序执行包括一些组合函数、大数、有限域函数、置换群、子群格计算和可解群中找到正规化子等等
255.vortex
C
面向对象数据库
源自一个单用户面向对象的数据库交易测试VORTEx,事务包括在数据库中创建一项、删除一项和数据的查找。包括三个不同的数据库,邮件列表、零件目录表和几何数据
256.bzip2
C
压缩
源自Julian Seward的bzip2,一个块排序的数据压缩算法,所有的压缩和解压缩在内存中进行。三个输入:大的tiff图像文件,程序二进制文件,源代码的tar文件
300.twolf
C
布局布线模拟器
芯片的布局和标准单元的全局连线的模拟。布局的问题是排列,采用模拟退火作为启发式方法来寻找最好的解决方法。全局路由使用了构造算法和迭代改进对子微芯片进行互连
浮点测试程序
语言
分类
描述
168.wupwise
Fortran 77
物理,量子色动力学
Wuppertal Wilson Ferminon求解器的简称,属于量子色动力学的格点规范理论领域。通过BiCGStab迭代的方法,求解了非齐次Lattice-Dirac方程
171.swim
Fortran 77
浅水建模
天气预报程序,用于测试当前高性能计算机的性能。实现浅水方程的有限差分的动力学模型,处理1335x1335范围的数据数组,迭代512次
172.mgrid
Fortran 77
三维势场多栅格求解器
使用一个简单的多网格求解器来计算一个三维的线性势场,源自NAS并行测试程序,对可移植性进行修改并采用不同的工作负载
173.applu
Fortran 77
抛物线/椭圆偏微分方程
模拟了三维逻辑结构化网格上的5个耦合非线性偏微分方程(PDE),使用了隐式伪时间匹配方法,基于稀疏雅可比矩阵的双因子近似因子分解方法
177.mesa
C
三维图形库
Mesa是一个开源的OpenGL实现库,支持通用帧缓冲,能配置为与OS和窗口系统无关。测试浮点、标量和内存的性能。输入为二维标量域,输出为PNG的二维图像文件
178.galgel
Fortran 90
计算流体动力学
源自GAMM测试程序的特定实例。低普朗特数流体在对流时发生震荡失稳的数值分析,计算稳态到失稳的临界格拉晓夫数和临界频率,使用频谱伽略金方法
179.art
C
图像识别,神经网络
使用自适应共振理论2(ART 2)神经网络方法,在温度图像中识别对象。对象是直升飞机或者飞机,神经网络首先在对象上进行训练,训练结束后,在扫描域图像中去找已经学习过的图像
183.equake
C
地震波传播模拟
使用有限元方法在非结构化网格中计算波长,模拟了地震时弹性波的传播,在大的、异构的山谷,如加州的圣费尔南多山谷或洛杉矶盆地,计算弹性波的传播,目标是能识别地震引起地表运动的历史时间
187.facerec
Fortran 90
图像处理,人脸识别
人脸的学习,从典范图像中抽取图像。对于每个要探测的图库,与相册图库进行匹配,查找相似性结果向量,寻找最大值,识别出相关图像
188.ammp
C
计算化学
在水包裹的混合蛋白质抑制剂环境下计算分子动力学,求解ODE方程,它是系统中原子运动的牛顿方程。该程序所模拟的蛋白质是HIV蛋白酶和茚地那韦抑制剂混合物
189.lucas
Fortran 90
数论,素数测试
执行了Lucas-Lehmer测试,检查梅森数的素数性2p-1。通过离散加权转换技术来计算梅森mod平方,使用数据局部的FFT算法来执行Lucas-Lehmer迭代的大定点数的平方
191.fma3d
Fortran 90
有限元碰撞模拟
这个程序计算非弹性、三维固体和结构在受到冲击或者突然加上负载时的瞬态动态响应行为。该程序构建了元素库,也对真实物质的特性进行建模,如弹性和塑性等
200.sixtrack
Fortran 77
高能核物理加速器设计
模拟在粒子加速器模型中,跟踪变化数量的粒子,以及变化数量的粒子发生了翻转。加速器的模型,如大型强子对撞机(LHC),可以用于动力学孔径的检查,如梁的长期稳定性
301.apsi
Fortran 77
气象学,污染物分布
求解中尺度天气系统的和天气的变化,包括位温,U和V风的分量,中尺度的垂直速度,W压力,污染物浓度分布C,源Q。采用劈分算法对整个系统进行求解
+
+
+
+
+
+表 12.7: SPEC CPU2006程序及描述
-
-
-
-表 12.8: SPEC CPU2017程序及描述
+ 定点测试程序
语言
分类
描述
400.perlbench
C
Perl编程语言
Perl脚本程序,包括垃圾邮件检测程序SpamAssassin、邮件索引器MHonArc和specdiff
401.bzip2
C
压缩
bzip2压缩程序,输入包括JPEG图片、源代码、HTML文件和混合文件等,使用不同的压缩级别进行压缩和解压缩
403.gcc
C
C编译器
基于gcc 3.2编译器,产生Opteron的代码
429.mcf
C
组合优化
一个用于大型公共交通的单车场车辆调度程序,使用了商业产品中常用的网络单纯形算法
445.gobmk
C
人工智能,围棋go
下围棋游戏,读入围棋格式文件,然后执行一些命令,根据围棋规则分析当前的局势,决定更为有利的下一步走法
456.hmmer
C
基因序列搜索
使用Profile隐马尔可夫模型(HMM),进行蛋白质基因序列分析
458.sjeng
C
人工智能,棋类游戏
一个排名很高的棋类游戏,属于棋类树的搜索和模式识别,通过组合alpha-beta剪枝或proof-number树搜索、形式判断、启发式前向修剪来寻找最佳的棋子移动
462.libquantum
C
物理,量子计算
模拟了一台量子计算机,执行了Shor多项式时间因式分解算法
464.h264ref
C
视频压缩
视频压缩标准H.264/AVC的一种参考设计,基于h264avc 9.3版本,使用两个参数组对YUV格式源文件进行H.264编码
471.omnetpp
C++
离散时间模拟
使用OMNet++离散时间模拟器对一个大型的校园网络进行建模,包括8000台计算机和900个交换机以及各种以太网协议
473.astar
C++
路径寻找算法
二维地图的寻路算法库,常用于游戏中的人工智能,实现了三种不同类型的A*算法
483.xalancbmk
C++
XML处理
将XML文档转换为HTML、纯文本或者其他XML格式的文档。是Xalan-C++的一个修改版本,Xalan-C++实现了XSL转换和XML Path语言
浮点测试程序
语言
分类
描述
410.bwaves
Fortran
流体动力学
对三维瞬间跨音速粘性流体层流冲击波的模拟计算。该算法实现了非压缩Navier-Stokes等式的全隐式无分裂求解,采用了Bi-CGstab算法,迭代求解了非对称线性方程组
416.gamess
Fortran
量子化学
源自量子化学计算用得最广的GAMESS(从头计算量子化学程序)。采用三种SCF自洽场方法(RHF、ROHF、MCSCF)对胞嘧啶分子、水和Cu2+离子、三唑离子进行自洽场计算
433.milc
C
物理,量子色动力学(QCD)
来自MIMD格计算(MILC)、四维SU(3)格点规范理论的模拟,用来研究QCD量子色动力学、夸克和胶子,采用了su3imp程序的串行版本
434.zeusmp
Fortran
物理,计算流体动力学(CFD)
伊利诺伊大学香槟分校开发的计算流体动力学程序,用于模拟天体物理现象。模拟了一个沿着X轴方向统一磁场中的三维冲击波
435.gromacs
C/ Fortran
生物化学,分子动力学
计算数百到数百万粒子的牛顿运动方程。模拟了一个在水和离子溶液中的蛋白质溶菌酶结构在各种实验手段如核磁共振的X光照射下的变化
436.cactusADM
C/ Fortran
物理,广义相对论
采用交错超越的数值分析方法,对时空曲率由内部物质决定的爱因斯坦演化方程进行求解,演化方程由10个标准的ADM 3+1分解的二阶非线性偏微分方程组成
437.leslie3d
Fortran
流体动力学
来自三维大涡模拟和线性涡流模型,其使用麦科马克预测校正时间积分方法,用来计算湍流的计算流体力学程序。计算了一个如燃油注入燃烧室的时间分层混合流体
444.namd
C++
生物学,分子动力学
源自并行程序NAMD,其模拟了大规模的生物分子系统。namd.input模拟了92224个原子组成的A-I载脂蛋白,ref有38次迭代
447.dealII
C++
有限元分析
计算自适应有限元和误差估计。对非常系数的亥姆霍兹方程进行求解,使用了基于二元加权误差估计生成最佳网络的自适应方法
450.soplex
C++
线性规划,优化
使用了单纯型算法和松弛线性算法求解线性方程。测试模拟包括铁路规划和军用空运规划的物理模型
453.povray
C++
图像光线跟踪
光线跟踪是一种渲染技术,通过模拟真实世界的光线传输方式计算实景的影像。该计算基于柏林噪声函数,渲染一幅1280x1024的反锯齿国际象棋棋盘图像
454.Calculix
C/ Fortran
结构力学
使用SPOOLES求解器库,进行线性和非线性三维结构力学的有限元分析。计算了一个高速旋转压气盘在离心力作用下的应力和变形情况
459.GemsFDTD
Fortran
计算电磁学
使用了FDTD(有限差分时域)方法求解三维时域中的麦克斯韦方程,计算了一个理想导体的雷达散射界面
465.tonto
Fortran
量子化学
面向对象的从头算量子化学包。模拟了量子晶体学领域计算,设置约束于分子的HF波函数,更好地匹配X光衍射实验数据
470.lbm
C
流体动力学
实现了格子玻尔兹曼方法(LBM),模拟在三维空间的非压缩流体,常用于材料科学中模拟流体的行为
481.wrf
C/ Fortran
天气预报
基于WRF模型,是下一代中等规模数值天气预报系统,用于天气预报和大气研究,模拟30公里区域和两天内的天气
482.sphinx3
C
语音识别
基于卡内基-梅隆大学开发的著名的语音识别软件,使用了AN4数据库,输入为原始的音频格式,输出为识别出来的话语
+
+
+
+
+
+表 12.8: SPEC CPU2017程序及描述
-
+SPECrate 2017定点程序
SPECspeed 2017定点程序
语言
描述
500.perlbench_r
600.perlbench_s
C
文本处理,Perl解释器
502.gcc_r
602.gcc_s
C
编译,GNU C编译器
505.mcf_r
605.mcf_s
C
组合和优化,求解车辆调度问题
520.omnetpp_r
620.omnetpp_s
C++
计算机网络,离散事件模拟
523.xalancbmk_r
623.xalancbmk_s
C++
通过XSLT将XML转换为HTML
525.x264_r
625.x264_s
C
视频压缩,x264视频编解码
531.deepsjeng_r
631.deepsjeng_s
C++
人工智能,下棋程序,alpha-beta树搜索
541.leela_r
641.leela_s
C++
人工智能,下棋程序(go),蒙特卡洛树搜索
548.exchange2_r
648.exchange2_s
Fortran
人工智能,9x9数独,递归方式求解
557.xz_r
657.xz_s
C
压缩和解压缩,xz压缩程序
SPECrate 2017浮点程序
SPECspeed 2017浮点程序
语言
描述
503.bwaves_r
603.bwaves_s
Fortran
计算流体动力学,爆炸建模
507.cactuBSSN_r
607.cactuBSSN_s
C++, C, Fortran
物理,广义相对论和数值相对论,求解真空中的爱因斯坦方程
508.namd_r
C++
结构生物学,模拟大规模的生物分子系统
510.parest_r
C++
分子医学成像,光学层析成像问题的有限元求解器
511.povray_r
C++, C
计算机可视化,光线追踪应用POV-Ray
519.lbm_r
619.lbm_s
C
流体动力学
521.wrf_r
621.wrf_s
Fortran, C
天气预报建模,基于新一代中尺度数值天气预报系统WRF
526.blender_r
C++, C
三维渲染和动画,基于开源的三维制作套件Blender
527.cam4_r
627.cam4_s
Fortran, C
大气环流建模,地球系统模型CESM中的大气建模部分
628.pop2_s
Fortran, C
大规模海洋建模(气候层面),地球系统模型CESM中的海洋建模部分
538.imagick_r
638.imagick_s
C
图像处理,图像处理软件包ImageMagick中convert部分
544.nab_r
644.nab_s
C
分子动力学,基于生命科学计算领域中分子建模应用NAB(核酸构建器)
549.fotonik3d_r
649.fotonik3d_s
Fortran
计算电磁学,利用时域有限差分方法计算光子波导的透射系数
554.roms_r
654.roms_s
Fortran
区域海洋建模,基于区域海洋建模系统ROMS
+
+
+
SPEC CPU2000包括12个定点测试程序(CINT2000)和14个浮点测试程序(CFP2000),定点测试程序包括C编译器、VLSI布局和布线工具、图形应用等。浮点测试程序包括量子色动力学、有限元建模和流体动力学等。测试机的内存应不小于256MB,以确保满足所有程序运行时的内存需求。
SPEC CPU2006中对SPEC CPU2000中的一些测试程序进行了升级,并抛弃和加入了一些测试程序,以更好地反映当时主流应用的特性。SPEC CPU2006包括了12项整数运算和17项浮点运算。SPEC CPU2006的工作集变大了一些,对测试机的最小内存需求是1GB。
SPEC CPU2017是SPEC组织于2017年再次更新的CPU基准测试程序集,其包含43个基准测试程序,分为4个测试程序集,两个定点程序集:SPECrate 2017 Integer、SPECspeed 2017 Integer,两个浮点程序集:SPECrate 2017 Floating Point和SPECspeed 2017 Floating Point。CPU2017工作集变得更大了,如SPECspeed需要最小16GB内存,SPECrate在64位系统中每份拷贝最小需要2GB内存。
@@ -1005,11 +1410,61 @@ void main(int argc, char **argv)
1992年,斯坦福大学推出了并行测试程序SPLASH(Stanford ParalleL Applications for SHared memory),1995年推出了SPLASH-2。SPLASH-2使用C语言编写,由12个程序组成,使用Pthreads API并行编程模式。SPLASH-2包含4个核心程序:Cholesky将一个稀疏矩阵拆分成一个下三角矩阵和它的转置的积;FFT用于计算快速傅里叶变换;Radix是分配存储地址的算法;LU用于将一个稀疏矩阵拆分成一个下三角矩阵和一个上三角矩阵的积。另外还包含8个应用程序:Ocean用于通过海洋边缘的海流模拟整个海洋的运动;Radiosity用于模拟光线在不同场景下的光影现象;Barnes用于模拟一个三维多体系统(例如星系);Raytrace使用光线追踪渲染了三维的场景;FMM采用了自适应快速多极子方法模拟了两维体系统的相互作用;Volrend使用光线投射算法渲染三维体;Water-Nsquared采用预测校正法评价了水分子系统的力和势能;Water-Spatial采用了三维格点算法评价了水分子系统的力和势能。
(2)PARSEC
2008年,普林斯顿大学推出了PARSEC(The Princeton Application Repository for Shared-Memory Computers)。它最早来自于Intel公司和普林斯顿大学的合作项目,目标是提供一个开源的并行测试程序集,主要面向新兴的应用,能评价多核处理器和多处理器系统,应用包括金融计算、计算机视觉、物理建模、未来媒体、基于内容的搜索和重复数据删除等。2009年推出PARSEC 2.1版本,PARSEC 2.1包括13个应用。2011年推出PARSEC 3.0,PARSEC 3.0支持网络的应用,以及更便利地增加新的工作负载。PARSEC能支持多种输入集,包括test、simdev、simsmall、simmedium、simlarge和native,sim输入集主要用于输入到模拟器的程序测试,native输入用于在多核和多处理器的真实的机器中进行测试。PARSEC通常采用Ptherads、OpenMP和Intel TBB三种并行编程模式,表12.9给出了PARSEC的应用和并行模拟、并行粒度、数据共享和数据交换的量以及每个应用所支持的并行编程模式。
-
-
-表 12.9: PARSEC并行测试程序集
+
+
+表 12.9: PARSEC并行测试程序集
-
+程序
应用领域
并行模式
并行粒度
数据共享
数据交换
Pthreads
OpenMP
TBB
blackscholes
金融分析
数据并行
粗粒度
低
低
X
X
X
bodytrack
计算视觉
数据并行
中等粒度
高
中等
X
X
X
canneal
工程类
非结构化
细粒度
高
高
X
dedup
企业存储
流水线
中等粒度
高
高
X
facesim
动画
数据并行
粗粒度
低
中等
X
ferret
相似性查找
流水线
中等粒度
高
高
X
fluidanimate
动画
数据并行
细粒度
低
中等
X
X
freqmine
数据挖掘
数据并行
中等粒度
高
中等
X
raytrace
渲染
数据并行
中等粒度
高
低
X
streamcluster
数据挖掘
数据并行
中等粒度
低
中等
X
X
swaptions
金融分析
数据并行
粗粒度
低
低
X
X
vips
媒体处理
数据并行
粗粒度
低
中等
X
x264
媒体处理
流水线
粗粒度
高
高
X
+
+
+
(3)Linpack
Linpack是线性系统软件包(Linear system package)的缩写,开始于1974年,由美国阿贡国家实验室应用数学所主任Jim Pool提出并设计,是一套专门解线性系统问题的数学软件。Linpack用于用高斯消元法求解一元N次稠密线性代数方程组的测试,当前在国际上已经成为最流行的用于测试高性能计算机系统浮点性能的基准测试程序。Linpack测试包括三类,Linpack100、Linpack1000和HPL。Linpack100求解规模为100阶的稠密线性代数方程组,它只允许采用编译优化选项进行优化,不得更改代码,甚至代码中的注释也不得修改。Linpack1000求解规模为1000阶的线性代数方程组,达到指定的精度要求,可以在不改变计算量的前提下做算法和代码的优化。HPL即High Performance Linpack,也叫高度并行计算基准测试。前两种测试运行规模较小,已不适合现代计算机的发展,因此现在使用较多的测试标准为HPL。HPL是针对现代并行计算机提出的测试方式,用户在不修改任意测试程序的基础上,可以调节问题规模的大小N(矩阵大小)、使用到的CPU数目和使用各种优化方法等来执行该测试程序,以获取最佳的性能。衡量计算机性能的一个重要指标就是计算峰值,浮点计算峰值是指计算机每秒能完成的浮点计算最大次数。理论浮点峰值是该计算机理论上能达到的每秒能完成的浮点计算最大次数,它主要是由CPU的主频决定的。理论浮点峰值=CPU主频×CPU每个时钟周期执行浮点运算的次数×系统中CPU核数。实测浮点峰值是指Linpack测试值,也就是在这台机器上运行Linpack测试程序,通过各种调优方法得到的最优的测试结果。用高斯消元法求解线性方程组,当求解问题规模为N时,浮点运算次数为(2/3 * N3+2N2)。因此,只要给出问题规模N,测得系统计算时间T,系统的峰值=计算量(2/3 N3+2*N2)/计算时间T,测试结果以浮点运算每秒(FLOPS)给出。一般程序的运行几乎不可能达到Linpack的实测浮点峰值,更不用说达到理论浮点峰值了。这两个值只是作为衡量机器性能的一个指标,用来表明机器的处理能力和潜能。
@@ -1029,11 +1484,61 @@ void main(int argc, char **argv)
12.3 性能分析方法
“曾经有那么一段时间,高性能处理器的结构完全由一个智者通过拍脑袋来决定,这段时间已经一去不复返了。现代处理器的结构是由一个队伍持续地改进和创新的结果,开发未来Alpha处理器的队伍是受到性能建模的方法所指导的。”这一段话源自Alpha处理器设计者所写的关于处理器性能分析的文章。现代处理器的设计者们需要采用高度系统化的流程来保证其设计能充分利用技术的进步并尽可能地适应目标工作负载的特性。计算机系统的设计涉及几个步骤。首先,理解和分析机器中要执行的应用或工作负载的特性,然后提出创新性的可选设计方案,接着对所提出的方案进行性能评估,最后选择最好的设计。但实际问题不像步骤流程所描述的那么简单,因为存在大量的可选设计方案,这些方案又和海量的各种工作负载交织在一起,这就导致设计和选择阶段变得非常棘手。
图表12.10列出了性能分析和评估技术的分类,主要可以分为两类:性能建模和性能测量。性能建模又可以细分为基于分析和统计的建模和基于模拟的建模。性能建模主要用于设计过程的早期阶段,那个阶段还没有实际系统或者实际系统还不能用于性能评估,这时一些设计的决策都是基于性能模型。分析建模根据对处理器结构以及程序特性的分析,用一定的方法建立处理器的性能公式,然后将体系结构参数及程序特性参数作为输入,用数学公式计算出处理器的性能信息,通常使用数学的方法来创建概率模型、队列模型、马尔可夫模型或者Petri网模型。模拟建模是处理器设计中用得最为广泛的方法,采用模拟器进行性能建模,用于预测现有的或者一个新的系统的性能。模拟器的优点是灵活和开销小,通常用高级语言编写,其缺点是需要大量的时间或大量的计算资源来精准地建模一个相对较大和复杂的系统。通常,模拟器比真实硬件的速度慢几个数量级,模拟的结果也会受到人为因素的干扰,因为模拟器会带来一些非真实的假设。相对于模拟器而言,基于实际机器或者原型系统的性能测量方法会更为精准一些。性能测量是最可信的方法,可以精准地评估系统的性能。实际系统出来之后,性能分析和测量也是非常有价值的:一是需要理解在各种真实工作负载下的实际系统的性能,二是要验证实际机器硬件是否和设计规范要求的性能相吻合,三是发现可能的性能瓶颈以及识别出未来设计中需要做出的修改。实际机器的测量还可以帮助验证设计阶段采用的性能模型,提供了一个附加的反馈用于未来的设计。性能测量的主要缺点是只能测量现有配置的性能,而系统的现有配置通常不能改变,或者只能有限的重新配置。性能测量可以进一步分为片上硬件监测、片外硬件监测、软件监测和微码插桩。
-
-
-表 12.10: 性能分析和评估技术的分类
+
+
+表 12.10: 性能分析和评估技术的分类
-
+性能建模
分析建模
概率模型
队列模型
马尔可夫模型
Petri网模型
模拟建模
踪迹驱动模拟
执行驱动模拟
全系统模拟
事件驱动模拟
统计方法模拟
性能测量
片上硬件监测器(例如性能计数器)
片外硬件监测器
软件监测器
微码插桩
+
+
+
好的性能建模和性能测量的技术和工具需要具备以下特性:
1)性能模型和测量结果要比较精确,因为这些性能结果会直接影响到设计的权衡和选择。
2)性能模型的速度要比较快,如果一个性能模型很慢,在这个模型上执行工作负载需要花费数小时或者数天,那么做性能分析工作则需要花费数星期或者数月,性能模型的速度越慢,设计空间的探索范围就越有限。
@@ -1068,29 +1573,181 @@ void main(int argc, char **argv)
1.硬件性能计数器
所有的高性能处理器和主流的嵌入式处理器,包括龙芯3A2000及其以后的处理器、Intel公司的Core系列、AMD公司的K10和Bulldozer系列、IBM公司的Power系列和ARM公司的Cortex A系列处理器,都在片上集成了硬件性能计数器。硬件性能计数器能捕获处理器内部或者外部的各种性能事件,能在执行复杂、真实的应用时捕获处理器的性能行为和应用的行为。这种能力克服了模拟器上的限制,因为模拟器通常不能执行复杂的应用。当前,复杂的运行时系统涉及大量同时执行的应用,通过硬件性能计数器的方法可以很好地评估系统和实时地监测系统的行为。所有的处理器厂商都会在处理器用户手册上公布其性能计数器的信息。
在Intel的Nehalem处理器中,每个处理器核(Core)有3个固定的性能计数器,还有4个通用的性能计数器,每个处理器都有一个核外(Uncore)的性能监测单元(PMU),包括8个通用的性能计数器和一个固定的性能计数器。在Intel处理器中,性能计数器被称为型号特定寄存器(Model Specific Register,简称MSR),包括性能事件选择寄存器(PerfEvtSel0、PerfEvtSel1等)和性能计数寄存器PMC(PerfCtr0、PerCtr1等)两种,PerfEvtSel寄存器能使用RDMSR和WRMSR两条指令进行读和写,这两条指令只能在内核态执行,PerfCtr计数寄存器能使用RDPMC指令进行读取。PerfEvtSel寄存器主要用于选择需要监视哪些事件。PMC寄存器主要用于对所选择的事件进行计数,当PMC寄存器发生溢出时,会触发APIC中断,操作系统可以提供相应的中断向量用于处理计数器溢出产生的中断,通常需要在中断描述符表或中断向量表中提供一项,用于指向相应的例外处理函数。表12.11和表12.12分别给出了Intel Nehalem处理器和龙芯3A5000处理器的性能计数器部分事件示例。
-
-
-表 12.11: Intel Nehalem处理器性能计数器事件和描述
+
+
+表 12.11: Intel Nehalem处理器性能计数器事件和描述
-
-
-
-表 12.12: 龙芯3A5000处理器性能计数器事件和描述
+ 事件号
umask值
事件
描述
3CH
00H
UnHalted Core cycles
时钟周期或拍数
C0H
00H
Instruction retired
提交的指令数
2EH
4FH
LLC reference
访问最后一级Cache的数目
2EH
41H
LLC misses
访问最后一级Cache失效的数目
C4H
00H
Branch Instruction Retired
提交的分支指令的数目
C5H
00H
Branch Misses Retired
提交的误预测的分支指令的数目
0BH
01H
MEM_INST_RETIRED.LOADS
提交的load指令的数目
0BH
02H
MEM_INST_RETIRED.STORES
提交的store指令的数目
0EH
01H
UOPS_ISSUED.ANY
从重命名表发射到保留站的微码数目
0FH
02H
MEM_UNCORE_RETIRED.OTHER_CORE_L2_HITM
提交的load访存操作,命中芯片相邻核二级Cache,并在Modified状态
12H
01H
SIMD_INT_128.PACKED_MPY
128位的SIMD定点乘法操作的数目
24H
01H
L2_RQST.LD_MISS
二级load请求,二级Cache失效,二级load包括L1D失效和L1D预取
26H
FFH
L2_DATA_RQSTS.ANY
所有的二级数据请求
40H
0FH
L1D_CACHE_LD.MESI
所有的一级数据Cache读请求
C4H
00H
BR_INST_RETIRED.ALL_BRANCHES
提交的分支指令
D2H
0FH
RAT_STALLS.ANY
寄存器分配表引起的堵塞
2AH
01H
UNC_QMC_OCCUPANCY.CH0
内存控制器通道0读请求发生
60H
01H
UNC_DRAM_OPEN.CH0
DRAM通道0由于读或者写发出open命令,因为该页首先需要打开
+
+
+
+
+
+表 12.12: 龙芯3A5000处理器性能计数器事件和描述
-
+事件号
事件名称
事件定义
00H
clkcnt
时钟周期数
01H
roq_cmtcnt
提交指令数
02H
brq_branch
brq返回分支指令数
03H
brq_err_branch
brq返回预测错误分支指令数
04H
dtlb_access_cnt
数据tlb访问次数
08H
dcache_access
一级数据Cache访问次数
09H
dcache_miss
一级数据Cache缺失次数
0AH
vcache_access
victim_cache访问次数
0BH
vcache_miss
victim_cache缺失次数
0CH
scres_total
三级Cache访问次数
0DH
scres_miss
三级Cache缺失次数
24H
roq_vecfp_cmtcnt
处理器提交向量浮点运算指令数
25H
roq_vecint_cmtcnt
处理器提交向量定点运算指令数
27H
roq_ex_cnt
处理器中例外次数
29H
brq_bhtbrq
返回条件跳转类分支指令数
2AH
brq_err_bht
brq返回条件跳转类错误预测分支指令数
2FH
ade_ualign_cnt
发生不对齐访问错误次数
32H
roq_load_cmtcnt
处理器提交load指令数
33H
roq_store_cmtcnt
处理器提交store指令数
34H
roq_scaint_cmtcnt
处理器提交标量定点运算指令数
40H
roq_cmt_4inst
提交阻塞周期数
41H
dec_deliver_stall
前端阻塞周期数
42H
be_stall
后端阻塞周期数
4CH
fxq_stall
定点发射队列阻塞周期数
4EH
mmq_stall
访存发射队列阻塞周期数
4FH
ftq_stall
浮点发射队列阻塞周期数
+
+
+
性能测量和分析对于寻找处理器设计中的性能瓶颈、查找引发性能问题的原因以及代码优化具有重要的意义。表12.13给出了主流的基于硬件性能计数器的性能分析工具。Intel商业化的性能分析软件Vtune能使用Intel的性能计数器来进行性能测量。Compaq公司的DCPI(连续的剖析平台)在Alpha处理器上也是一个强大的程序剖析工具。系统工具Perf-mon能使用UltraSPARC处理器的性能计数器收集统计信息。Perf是Linux内核自带的用于收集和分析性能数据的框架,有时称为Perf事件或者Perf工具,早期称为Performance Counters for Linux(PCL),能用于统计指令执行数目、Cache失效数目、分支误预测失效数目等性能事件,也可以用于跟踪动态控制流,进行函数级和指令级的热点识别。OProfile是Linux系统的性能分析工具,是一个开源的profiling工具,它使用性能计数器来统计信息。OProfile包括一个内核模块和一个用户空间守护进程,前者可以访问性能计数寄存器,后者在后台运行,负责从这些寄存器中收集数据。AMD公司开发的CodeAnalyst是基于OProfile和Perf的开源的图形化性能分析工具,CodeAnalyst支持AMD的基于指令的采样技术(IBS),能更为精确地定位导致Cache失效或者流水线中断的指令。PAPI由田纳西大学开发,提供了一套访问性能计数器的API,使用PAPI需要对源码进行插桩,并且需要安装perfctr内核模块。
-
-
-表 12.13: 处理器性能分析工具
+
+
+表 12.13: 处理器性能分析工具
-
+工具
平台
链接
Intel Vtune
Intel X86
http://software.intel.com/intel-vtune-amplifier-xe
Linux perf
X86/MIPS等
http://perf.wiki.kernel.org
oprofile
X86/MIPS等
http://oprofile.sourceforge.net
DCPI
Alpha
http://www.hp.com/openvms/products/dcpi
Perf-mon
UltraSPARC
http://www.sics.se/~mch/perf-monitor/index.html
AMD CodeAnalyst
AMD X86
http://developer.amd.com/tools-and-sdks/compute__trashed/amd-codeanalyst-performance-analyzer-for-linux/
PAPI
X86
http://icl.cs.utk.edu/papi/software/index.html
+
+
+
下面主要介绍Linux平台中最常用的一种性能分析工具Perf。
2.Perf性能分析工具
Perf是Linux中集成在Linux内核中的性能分析工具,也称为perf_events或者perf tools,从Linux内核2.6.31开始集成到内核中。Perf用于收集和分析性能数据,在用户态就可以对应用进行剖析。其实现包括两个部分,用户态部分为perf程序,内核部分包括对处理器硬件计数器的操作、计数器溢出中断处理程序以及新增的系统调用sys_perf_event_open。依赖硬件平台提供的硬件事件支持或者内核提供的软件事件支持,perf能够对CPU和操作系统性能相关的指标进行采样,用较低的开销收集运行时性能数据。硬件支持的事件包括提交的指令、执行的时钟周期数、Cache失效次数和分支误预测次数等,软件的事件包括进程上下文切换、缺页、任务迁移和系统调用的次数等。通过将采样的事件和采样时的指令地址对应,perf可以识别哪段代码出现某种事件的频率较高,从而定位热点代码。通过各种事件统计,Perf也能监测程序在操作系统层面对资源的利用情况,协助理解内核和操作系统的行为。
Perf的工作原理如图12.2所示。Perf通过调用sys_perf_event_open系统调用来陷入内核,采样硬件事件时,内核根据Perf提供的信息在CPU的性能监测单元(Performance Monitoring Unit,简称PMU)上初始化硬件性能计数器(Performance Monitoring Counter,简称PMC)。每当特定事件发生时,PMC会随着增加,当PMC发生溢出时,会产生性能计数器溢出中断(Performance Counter Interrupt,简称PCI)。内核在PCI的中断处理程序中记录PMC的值,以及产生中断时正在执行的指令所在地址PC、当前时间戳以及当前进程的进程号PID或线程号TID、程序的符号名称comm等数据,把这些数据作为一个采样。内核把记录的采样拷贝到环形缓冲区中,位于用户态的perf利用mmap内存映射从环形缓冲区复制采样数据,并对其进行解析。Perf按照PID、comm等数据定位相应的进程,按照指令地址PC和可执行与可链接格式(ELF)文件中的符号表定位产生PCI中断的指令所属函数。PMC会被中断处理程序按照初始化时设定的采样周期重新置位恢复初始化时的值,在后续的运行过程中再次开始计数、触发中断,直到整个Perf采样过程结束。
-
-
-图 12.2: Perf的工作原理图
+
+
+
+图 12.2: Perf的工作原理图
+
Perf是一个包含22种子工具的工具集,每个工具分别作为一个子命令。annotate命令读取perf.data并显示注释过的代码;diff命令读取两个perf.data文件并显示两份剖析信息之间的差异;evlist命令列出一个perf.data文件的事件名称;inject命令过滤以加强事件流,在其中加入额外的信息;kmem命令为跟踪和测量内核中slab子系统属性的工具;kvm命令为跟踪和测量kvm客户机操作系统的工具;list命令列出所有符号事件类型;lock命令分析锁事件;probe命令定义新的动态跟踪点;record命令运行一个程序,并把剖析信息记录在perf.data中;report命令读取perf.data并显示剖析信息;sched命令为跟踪和测量内核调度器属性的工具;script命令读取perf.data并显示跟踪输出;stat命令运行一个程序并收集性能计数器统计信息;timechart命令为可视化某个负载在某时间段的系统总体性能的工具;top命令为系统剖析工具。下面以示例的方式给出了一些常用子命令的用法。
perf list。perflist用来查看Perf所支持的性能事件,有软件的,也有硬件的。这些事件因系统性能监控硬件和软件配置而异。命令格式为:
@@ -1210,32 +1867,241 @@ mem:<addr>[:access] [Hardware breakpoint]
与1GHz主频的龙芯3A1000相比,2.5GHz主频的龙芯3A5000在主频只提升2.5倍的情况下实现了性能10倍提升。这个成绩背后是大量的性能分析和设计优化工作。本节以龙芯3A5000处理器的部分性能测试和分析工作为例,展示相关工具与方法的实际应用。
我们采用对比分析的方法,选择了两款采用类似工艺的X86处理器作为参考对象。对这三款处理器,我们先用SPEC CPU基准程序进行宏观性能测试,然后用perf工具收集SPEC CPU运行过程的微结构相关统计数据,用LMbench等微基准程序测量系统的一些关键参数,试图通过对比分析更好地理解不同设计对性能的影响,寻找下一代龙芯处理器可能的优化方向。
表12.14给出了这三款处理器的基本信息和主要设计参数。为了减少不必要的差异,三款处理器的频率被都设置为2.5G赫兹,测试软件也尽量采用相同的编译器及编译参数来编译。
-
-
-表 12.14: 三款处理器的基本信息和主要设计参数
+
+
+表 12.14: 三款处理器的基本信息和主要设计参数
-
+厂商
Loongson
AMD
Intel
处理器型号
3A5000
r3 1200
i3 9100f
上市时间
2021
2017
2019
工艺
12nm
14nm
14nm
指令集
LoongArch
X86-64
X86-64
微结构型号
GS464V
Zen1
SkyLake
频率
2.5GHz
2.5GHz
2.5GHz
内存类型和频率
DDR4 3200MHz
DDR4 3200MHz
DDR4 2400MHz
Cache层次
64KB以及ICache
+
64KB一级DCache
+
256KB二级Cache
+
16MB三级Cache
64KB一级ICache
+
32KB一级DCache
+
512KB二级Cache
+
8MB三级Cache
32KB一级ICache
+
32KB一级DCache
+
256KB二级Cache
+
6MB三级Cache
核心队列和重命名寄存器数量
128项ROB,64项load队列,48项store队列,32项分支队列,32项定点、32项浮点和32项访存保留站,128项定点和128项浮点重命名寄存器。
192项ROB,72项load队列,44项store队列,84项定点和96项浮点保留站,168项定点和160项浮点重命名寄存器。
224项ROB,72项load队列,56项store队列,97项统一保留站,180项定点和168项浮点重命名寄存器。
功能部件数
4个定点,2个访存部件,2个256位的浮点乘加
4个定点部件,2个访存部件,4个128位浮点部件(其中2个FMA/FMUL,2个FADD)
4个定点、3个访存、3个256位浮点乘加
+
+
+
这三款处理器总体微结构设计比较相近,但又各有特点。龙芯3A5000的“架子”(ROQ等队列大小和重命名寄存器数量等)明显小于其他两款,浮点功能部件数量少于其他两款,但它的三级Cache容量却是最大的;Skylake“架子”最大,有三个访存部件,但三级Cache容量最小,内存也相对慢。
12.4.1 SPEC CPU基准测试程序的分值对比
三款处理器的测试机采用的编译器均为GCC 8,使用基本相同(除个别处理器相关选项)的SPEC CPU peak优化配置文件。表12.15和12.16分别给出了三款处理器的SPEC CPU2006 speed和4核rate分值对比10。
-
-
-表 12.15: SPEC CPU2006 speed分值对比
+
+
+表 12.15: SPEC CPU2006 speed分值对比
-
-
-
-表 12.16: SPEC CPU2006 rate4分值对比
+ SPEC CPU2006
loongson 3A5000
zen1 r3-1200
skylake i3 9100f
zen1/3A5000
skylake/3A5000
400.perlbench
29.10
31.40
35.30
107.90%
121.30%
401.bzip2
17.30
18.50
19.20
106.90%
111.00%
403.gcc
23.90
30.80
38.10
128.90%
159.40%
429.mcf
27.10
27.60
37.00
101.80%
136.50%
445.gobmk
25.60
20.10
21.70
78.50%
84.80%
456.hmmer
39.20
46.20
54.70
117.90%
139.50%
458.sjeng
22.40
17.80
22.50
79.50%
100.40%
462.libquantum
78.80
141.00
123.00
178.90%
156.10%
464.h264ref
38.00
45.10
51.00
118.70%
134.20%
471.omnetpp
18.10
17.20
21.20
95.00%
117.10%
473.astar
19.30
15.60
15.80
80.80%
81.90%
483.xalancbmk
28.70
25.90
36.90
90.20%
128.60%
SPECint2006
27.87
29.05
33.47
104.20%
120.10%
410.bwaves
54.40
99.10
85.20
182.20%
156.60%
416.gamess
22.90
28.40
30.20
124.00%
131.90%
433.milc
18.00
38.80
32.30
215.60%
179.40%
434.zeusmp
25.00
51.00
56.20
204.00%
224.80%
435.gromacs
15.30
25.80
23.50
168.60%
153.60%
436.cactusADM
84.80
135.00
228.00
159.20%
268.90%
437.leslie3d
36.20
48.20
62.80
133.10%
173.50%
444.namd
20.70
21.50
23.80
103.90%
115.00%
447.dealII
39.80
48.70
46.70
122.40%
117.30%
450.soplex
28.50
34.60
39.80
121.40%
139.60%
453.povray
39.10
34.20
41.50
87.50%
106.10%
454.calculix
17.60
29.20
30.10
165.90%
171.00%
459.GemsFDTD
35.20
65.10
56.70
184.90%
161.10%
465.tonto
28.40
25.20
37.20
88.70%
131.00%
470.lbm
28.80
67.50
74.60
234.40%
259.00%
481.wrf
36.50
53.90
65.00
147.70%
178.10%
482.sphinx3
34.10
40.60
48.40
119.10%
141.90%
SPECfp2006
30.29
43.85
48.34
144.80%
159.60%
+
+
+
+
+
+表 12.16: SPEC CPU2006 rate4分值对比
-
+CPU2006 rate4
loongson 3A5000
Zen1 r3-1200
skylake i3 9100f
zen1/3A5000
skylake/3A5000
400.perlbench
102.00
125.00
136.0
122.50%
133.30%
401.bzip2
64.40
69.60
67.7
108.10%
105.10%
403.gcc
75.50
107.00
121.0
141.70%
160.30%
429.mcf
50.50
81.30
88.3
161.00%
174.90%
445.gobmk
95.80
79.70
85.4
83.20%
89.10%
456.hmmer
128.00
184.00
214.0
143.80%
167.20%
458.sjeng
86.00
71.30
89.2
82.90%
103.70%
462.libquantum
89.20
154.00
121.0
172.60%
135.70%
464.h264ref
147.00
178.00
201.0
121.10%
136.70%
471.omnetpp
45.00
54.80
60.0
121.80%
133.30%
473.astar
58.60
57.10
55.9
97.40%
95.40%
483.xalancbmk
68.60
90.20
111.0
131.50%
161.80%
SPECint2006
79.39
95.99
103.2
120.90%
129.90%
410.bwaves
82.10
210.00
152.0
255.80%
185.10%
416.gamess
84.80
114.00
119.0
134.40%
140.30%
433.milc
44.60
103.00
83.2
230.90%
186.50%
434.zeusmp
83.60
179.00
190.0
214.10%
227.30%
435.gromacs
58.30
103.00
92.2
176.70%
158.10%
436.cactusADM
114.00
193.00
256.0
169.30%
224.60%
437.leslie3d
68.30
114.00
96.0
166.90%
140.60%
444.namd
80.00
85.80
94.7
107.30%
118.40%
447.dealII
142.00
187.00
176.0
131.70%
123.90%
450.soplex
62.90
99.00
101.0
157.40%
160.60%
453.povray
149.00
136.00
155.0
91.30%
104.00%
454.calculix
62.00
116.00
118.0
187.10%
190.30%
459.GemsFDTD
53.40
98.20
80.7
183.90%
151.10%
465.tonto
106.00
98.70
136.0
93.10%
128.30%
470.lbm
51.10
99.40
87.3
194.50%
170.80%
481.wrf
92.10
175.00
164.0
190.00%
178.10%
482.sphinx3
81.30
125.00
139.0
153.80%
171.00%
SPECfp2006
78.65
126.28
124.9
160.60%
158.80%
+
+
+
基准测试程序的主要作用之一是评价目标产品的性能,比如SPEC CPU能够用于不同CPU之间的性能比较。通过上述数据,我们可以说3A5000定点性能和同频率的Zen处理器大致相当(单核平均相差4.2%),但浮点性能还有一定差距(单核平均相差44.8%)。
通过基准程序的比较分析可以发现一些可能的优化方向。例如,从上述数据我们不难发现,3A5000的浮点性能有比较明显的提升空间。可以在相关设计平台上尝试增加浮点功能数量来验证是否能够显著提升性能,如果单纯增加浮点部件还不够,可以继续试验扩大“架子”来进一步确定。事实上,设计中的下一代龙芯处理器在这两个方面都作了较大的改进,有望弥补这个弱点。同样,我们也可以看到,并不是每个SPEC程序的性能差异都是一样的,有些程序3A5000快,有些Zen1快,有些Skylake快。其中定点程序性能差距最大的是462.libquantum, Zen1比3A5000快78.9%,显著大于其它定点程序。我们进一步分析这个程序,发现这和编译器的自动并行化支持有关系。在关闭编译器的自动并行化选项时,差距缩小到16.5%,如表12.17所示。除了462,436和459等几个程序的自动并行化效果在3A5000和Zen1上也有类似情况。因此,龙芯3A5000平台的编译器自动并行化优化的实现很可能存在较大的优化空间。此外,3A5000与另外两款处理器的SPEC CPUrate分值差距比单核更大,这个和它拥有更大的三级Cache以及更高的访问访存速率不一致,需要进一步分析。类似这样,结合理论的推测来细致分析相关数据可以找到分析点,这里不再展开。
-
-
-表 12.17: SPEC CPU2006 speed分值对比(关闭自动并行化)
+
+
+表 12.17: SPEC CPU2006 speed分值对比(关闭自动并行化)
-
+SPEC CPU2006
loongson 3A5000
zen1 r3-1200
skylake i3 9100f
zen1/3A5000
skylake/3A5000
400.perlbench
28.90
31.20
35.20
108.00%
121.80%
401.bzip2
17.20
18.70
19.20
108.70%
111.60%
403.gcc
23.80
30.80
38.40
129.40%
161.30%
429.mcf
26.90
27.50
36.30
102.20%
134.90%
445.gobmk
25.50
20.00
21.60
78.40%
84.70%
456.hmmer
39.20
46.20
54.30
117.90%
138.50%
458.sjeng
22.20
17.80
22.50
80.20%
101.40%
462.libquantum
54.60
63.60
72.00
116.50%
131.90%
464.h264ref
37.90
45.10
51.00
119.00%
134.60%
471.omnetpp
18.00
17.20
21.00
95.60%
116.70%
473.astar
19.10
15.40
15.80
80.60%
82.70%
483.xalancbmk
28.10
26.00
36.80
92.50%
131.00%
SPECint2006
26.86
27.15
31.91
101.10%
118.80%
410.bwaves
54.80
98.90
85.40
180.50%
155.80%
416.gamess
22.20
28.60
30.20
128.80%
136.00%
433.milc
17.90
38.40
32.40
214.50%
181.00%
434.zeusmp
24.60
51.00
56.30
207.30%
228.90%
435.gromacs
15.20
25.80
23.20
169.70%
152.60%
436.cactusADM
52.00
58.60
93.10
112.70%
179.00%
437.leslie3d
36.20
48.30
62.30
133.40%
172.10%
444.namd
20.60
21.50
23.80
104.40%
115.50%
447.dealII
39.70
48.60
46.70
122.40%
117.60%
450.soplex
28.60
34.70
39.40
121.30%
137.80%
453.povray
38.10
34.10
40.90
89.50%
107.30%
454.calculix
17.60
29.30
30.20
166.50%
171.60%
459.GemsFDTD
29.80
40.20
45.70
134.90%
153.40%
465.tonto
28.10
25.20
37.10
89.70%
132.00%
470.lbm
28.50
66.80
74.80
234.40%
262.50%
481.wrf
32.10
56.80
64.90
176.90%
202.20%
482.sphinx3
33.50
40.80
48.60
121.80%
145.10%
SPECfp2006
28.72
40.69
45.20
141.70%
157.30%
+
+
+
基准程序的比较分析还可以帮助我们理解微结构设计与程序性能之间的关系。不同的程序对不同的微结构参数敏感,观察每个程序在不同处理器的表现,结合下节更多的微结构数据可以更好地理解它们之间的关系。当然,众多微结构参数往往会以复杂的形式互相影响,不能轻易作出结论。有条件的情况下(例如有校准过的性能设计模型或者快速仿真平台),最好通过每次只改变一个参数来观察确认。
@@ -1243,20 +2109,120 @@ mem:<addr>[:access] [Hardware breakpoint]
SPEC CPU的分数只能宏观体现一个程序的性能,我们需要收集更多微观运行时数据才能更好地了解程序的行为,比如程序执行了多少指令,花了多少时钟周期,有多少分支指令猜测错误,哪部分代码运行次数多,哪里Cache不命中最频繁等等。利用12.3节介绍的perf工具就能够收集这类数据,本节展示了SPEC CPU2006动态指令数、IPC和分支预测相关的几个指标的情况。前两个是CPU性能公式中的两大要素,分支预测则对现代高性能处理器的性能有较大影响。
1) 动态指令数
CPU的性能公式说明处理器的执行时间和CPI、时钟频率以及动态执行指令数相关。表12.18给出了3A5000、Zen1和Skylake三款处理器SPEC CPU2006动态执行指令数的对比,其中指令数目的单位为百万条,SPEC_int和SPEC_fp为定点程序和浮点程序指令数目的累积总和。
-
-
-表 12.18: SPEC CPU2006动态指令执行的数目(百万条)对比
+
+
+表 12.18: SPEC CPU2006动态指令执行的数目(百万条)对比
-
+SPEC CPU2006
loongson 3A5000
zen1 r3-1200
Skylake i3 9100f
zen1/3A5000
skylake/3A5000
400.perlbench
2,120,668
1,893,315
1,875,825
89.30%
88.50%
401.bzip2
2,598,456
2,230,210
2,105,917
85.80%
81.00%
403.gcc
918,503
720,416
637,262
78.40%
69.40%
429.mcf
270,116
273,830
279,889
101.40%
103.60%
445.gobmk
1,447,758
1,364,925
1,349,346
94.30%
93.20%
456.hmmer
1,725,941
1,293,236
1,140,944
74.90%
66.10%
458.sjeng
2,448,077
2,256,035
2,259,544
92.20%
92.30%
462.libquantum
1,287,345
1,354,349
1,355,304
105.20%
105.30%
464.h264ref
3,906,022
3,057,443
2,574,503
78.30%
65.90%
471.omnetpp
455,925
515,436
535,127
113.10%
117.40%
473.astar
804,288
862,191
855,804
107.20%
106.40%
483.xalancbmk
845,446
857,273
858,951
101.40%
101.60%
SPECint_2006
18,828,544
16,678,659
15,828,415
88.60%
84.10%
410.bwaves
867,049
643,946
563,805
74.30%
65.00%
416.gamess
6,326,795
4,818,900
4,737,215
76.20%
74.90%
433.milc
495,564
573,026
562,614
115.60%
113.50%
434.zeusmp
1,255,758
901,519
682,460
71.80%
54.30%
435.gromacs
1,523,919
1,606,588
1,621,933
105.40%
106.40%
436.cactusADM
1,059,689
913,079
460,976
86.20%
43.50%
437.leslie3d
586,968
663,348
350,522
113.00%
59.70%
444.namd
1,602,845
1,630,103
1,627,678
101.70%
101.50%
447.dealII
1,078,354
1,048,560
1,029,969
97.20%
95.50%
450.soplex
588,538
562,087
535,320
95.50%
91.00%
453.povray
715,602
816,008
838,778
114.00%
117.20%
454.calculix
1,941,778
1,615,746
1,473,997
83.20%
75.90%
459.GemsFDTD
773,507
832,552
471,723
107.60%
61.00%
465.tonto
2,028,792
2,212,799
1,674,174
109.10%
82.50%
470.lbm
877,312
817,999
821,754
93.20%
93.70%
481.wrf
1,437,760
953,405
794,095
66.30%
55.20%
482.sphinx3
2,229,029
1,850,550
1,867,714
83.00%
83.80%
SPECfp_2006
25,389,259
22,460,213
20,114,726
88.50%
79.20%
+
+
+
对于CPU2006,3A5000定点程序比Zen1的动态指令数(累积总和)要多12.9%,浮点程序指令数(累积总和)要多13.0%。和Skylake相比,3A5000定点程序指令数要多19%,浮点程序平均要多26.2%。定点程序中403.gcc、456.hmmer、464.h264ref的指令数3A5000比Zen1多25%以上,浮点程序中410.bwaves、416.gamess、434.zeusmp和481.wrf的指令数3A5000比Zen1多30%以上。对比3A5000和Zen1的CPU2006的分值和动态指令数之间的关系,基本上符合动态指令数差距越大,性能差距也就越大。
动态指令数与指令集和编译器都有密切的关系。X86是一种复杂指令集,在动态指令数方面总体比RISC指令集略有优势可以理解的,但超过20%的差距都值得认真查看。例如,以上数据中,436.cactusADM的动态指令数3A5000比Skylake多超过一倍,即使是Zen1也比Skylake多将近一倍。查看相应的代码,可以发现主要是编译器的向量指令选择带来的差异。Skylake用256位向量指令,Zen1用128位。3A5000同样支持256位向量,但是其自动向量化支持尚不够优化,没有选择生成向量指令。按peak优化配置编译时编译选项会指定处理器的微结构型号,如-march=native或-march=loongarch,GCC编译器会根据处理器的微结构型号选取合适的指令。例如对同一段源代码,对于Zen1,编译器可能会选取128位的向量指令,因为256位的向量指令在Zen1内部需要被拆分,其效率有时候还不如128的向量指令,而对于Skylake,编译器可能会选取256位的向量指令,这也导致同样是X86指令集的Zen1和Skylake动态指令数的不同。分析动态指令数的差异原因,可以为编译器和指令集的优化提供直接线索。
2)IPC
动态执行的指令数目只是性能公式中的一个因素,CPU性能公式的第二个因素是IPC。表12.19是三款处理器CPU2006测试程序IPC的对比。
-
-
-表 12.19: SPEC CPU2006 IPC对比
+
+
+表 12.19: SPEC CPU2006 IPC对比
-
+SPEC CPU2006
loongson 3A5000
zen1 r3-1200
skylake i3 9100f
zen1/3A5000
skylake/3A5000
400.perlbench
2.54
2.45
2.72
96.50%
107.10%
401.bzip2
1.86
1.73
1.68
93.00%
90.30%
403.gcc
1.10
1.10
1.22
100.00%
110.90%
429.mcf
0.32
0.33
0.45
103.10%
140.60%
445.gobmk
1.42
1.05
1.12
73.90%
78.90%
456.hmmer
2.97
2.57
2.68
86.50%
90.20%
458.sjeng
1.81
1.33
1.68
73.50%
92.80%
462.libquantum
1.42
1.67
1.88
117.60%
132.40%
464.h264ref
2.69
2.50
2.38
92.90%
88.50%
471.omnetpp
0.54
0.57
0.73
105.60%
135.20%
473.astar
0.89
0.79
0.77
88.80%
86.50%
483.xalancbmk
1.40
1.30
1.84
92.90%
131.40%
SPECint_rate2006
1.34
1.24
1.40
92.90%
105.00%
410.bwaves
1.42
1.90
1.41
133.80%
99.30%
416.gamess
2.97
2.82
2.92
94.90%
98.30%
433.milc
0.40
0.97
0.81
242.50%
202.50%
434.zeusmp
1.39
2.01
1.68
144.60%
120.90%
435.gromacs
1.31
2.32
2.13
177.10%
162.60%
436.cactusADM
1.79
1.72
1.44
96.10%
80.40%
437.leslie3d
0.91
1.38
0.93
151.60%
102.20%
444.namd
1.66
1.75
1.94
105.40%
116.90%
447.dealII
1.52
1.79
1.70
117.80%
111.80%
450.soplex
0.84
0.94
1.02
111.90%
121.40%
453.povray
2.06
2.12
2.60
102.90%
126.20%
454.calculix
1.66
2.30
2.16
138.60%
130.10%
459.GemsFDTD
0.88
1.27
0.82
144.30%
93.20%
465.tonto
2.33
2.27
2.53
97.40%
108.60%
470.lbm
0.74
1.62
1.79
218.90%
241.90%
481.wrf
1.67
1.95
1.73
116.80%
103.60%
482.sphinx3
1.52
1.55
1.85
102.00%
121.70%
SPECfp_rate2006
1.34
1.73
1.62
129.70%
121.00%
+
+
+
从表中的数据可以看出3A5000的定点程序的IPC高于Zen1,略低于Skylake,3A5000的浮点程序的平均IPC低于Zen1和Skylake。
从IPC数据的演进可以看到处理器微结构的不断发展。对于SPEC CPU2006程序,3A2000和AMD K10的定点IPC平均为1左右,浮点IPC平均为1.1左右,而3A5000和Skylake的定点IPC平均提升到1.4左右,而Zen1的浮点IPC平均提升到1.73了。
同样,IPC数据的对比分析也可以提供一些优化线索。例如,可以找到同一程序在不同处理器IPC差异很大的情况,通过热点代码块比较、微结构行为数据统计等进一步分析相应的编译器和微结构是否存在瓶颈。
@@ -1264,68 +2230,374 @@ mem:<addr>[:access] [Hardware breakpoint]
- 分支预测
分支指令处理的效率对处理器性能的影响较大,衡量分支指令处理效率的指标通常包括分支误预测率和分支指令的吞吐率。表12.20、表12.21给出了三款处理器SPEC CPU2006测试程序分支误预测率和分支吞吐率的对比。
-
-
-表 12.20: SPEC CPU2006 分值误预测率对比
+
+
+表 12.20: SPEC CPU2006 分值误预测率对比
-
-
-
-表 12.21: SPEC CPU2006 分支吞吐率(每秒执行的百万条分支指令数)对比
+ SPEC CPU2006
loongson 3A5000
zen1 r3-1200
skylake i3 9100f
zen1/3A5000
skylake/3A5000
400.perlbench
1.18%
1.10%
0.71%
0.93
0.60
401.bzip2
5.38%
5.08%
4.94%
0.94
0.92
403.gcc
1.52%
1.31%
1.03%
0.86
0.68
429.mcf
5.62%
3.57%
3.86%
0.64
0.69
445.gobmk
8.29%
9.80%
8.56%
1.18
1.03
456.hmmer
1.12%
0.66%
0.65%
0.59
0.58
458.sjeng
4.47%
5.89%
4.17%
1.32
0.93
462.libquantum
0.79%
0.21%
0.09%
0.27
0.11
464.h264ref
2.09%
1.64%
1.69%
0.78
0.81
471.omnetpp
2.53%
1.65%
1.83%
0.65
0.72
473.astar
13.60%
12.03%
12.75%
0.88
0.94
483.xalancbmk
0.43%
0.51%
0.34%
1.19
0.79
SPECint_2006
2.48%
1.97%
1.64%
0.79
0.66
410.bwaves
0.09%
0.25%
0.15%
2.78
1.67
416.gamess
0.94%
1.07%
0.69%
1.14
0.73
433.milc
6.58%
0.40%
0.23%
0.06
0.03
434.zeusmp
1.38%
0.95%
0.12%
0.69
0.09
435.gromacs
7.04%
6.19%
6.11%
0.88
0.87
436.cactusADM
0.33%
1.49%
0.17%
4.52
0.52
437.leslie3d
0.33%
1.66%
0.22%
5.03
0.67
444.namd
4.66%
4.38%
4.52%
0.94
0.97
447.dealII
2.48%
2.31%
2.06%
0.93
0.83
450.soplex
5.51%
4.22%
4.40%
0.77
0.80
453.povray
1.86%
1.30%
0.56%
0.70
0.30
454.calculix
3.23%
2.80%
3.02%
0.87
0.93
459.GemsFDTD
0.29%
0.40%
0.10%
1.38
0.34
465.tonto
1.20%
0.93%
0.91%
0.78
0.76
470.lbm
0.46%
0.45%
0.38%
0.98
0.83
481.wrf
1.08%
0.51%
0.24%
0.47
0.22
482.sphinx3
2.35%
1.80%
1.88%
0.77
0.80
SPECfp_2006
1.30%
1.24%
0.65%
0.95
0.50
+
+
+
+
+
+表 12.21: SPEC CPU2006 分支吞吐率(每秒执行的百万条分支指令数)对比
-
+SPEC CPU2006
loongson 3A5000
zen1 r3-1200
skylake i3 9100f
zen1/3A5000
skylake/3A5000
400.perlbench
1,285.22
1,330.70
1,507.39
103.50%
117.30%
401.bzip2
687.20
679.21
700.24
98.80%
101.90%
403.gcc
528.81
625.04
726.01
118.20%
137.30%
429.mcf
259.12
186.98
251.16
72.20%
96.90%
445.gobmk
666.08
495.13
533.88
74.30%
80.20%
456.hmmer
561.53
553.70
645.47
98.60%
114.90%
458.sjeng
896.90
662.04
840.26
73.80%
93.70%
462.libquantum
1,109.34
1,225.50
1,380.07
110.50%
124.40%
464.h264ref
370.65
468.73
483.39
126.50%
130.40%
471.omnetpp
333.52
356.34
442.49
106.80%
132.70%
473.astar
512.46
354.04
349.51
69.10%
68.20%
483.xalancbmk
1,045.16
963.92
1,362.81
92.20%
130.40%
SPECint_2006
615.94
576.20
667.59
93.50%
108.40%
410.bwaves
106.04
112.99
94.37
106.60%
89.00%
416.gamess
489.11
592.77
616.36
121.20%
126.00%
433.milc
11.81
48.69
43.09
412.30%
364.90%
434.zeusmp
171.28
101.70
88.22
59.40%
51.50%
435.gromacs
130.19
174.94
158.70
134.40%
121.90%
436.cactusADM
7.02
12.71
13.05
181.10%
185.90%
437.leslie3d
134.20
300.84
208.51
224.20%
155.40%
444.namd
239.86
234.96
260.51
98.00%
108.60%
447.dealII
659.59
854.76
830.83
129.60%
126.00%
450.soplex
386.41
462.08
514.60
119.60%
133.20%
453.povray
738.40
889.04
1,060.83
120.40%
143.70%
454.calculix
233.14
351.24
352.48
150.70%
151.20%
459.GemsFDTD
70.57
136.25
111.24
193.10%
157.60%
465.tonto
409.28
426.25
513.51
104.10%
125.50%
470.lbm
24.27
57.78
63.32
238.10%
260.90%
481.wrf
323.44
460.50
504.50
142.40%
156.00%
482.sphinx3
420.30
432.66
546.20
102.90%
130.00%
SPECfp_2006
152.40
214.47
213.45
140.70%
140.10%
+
+
+
可以看到,这三款处理器总体分支误预测率的绝对值都已经比较小,skylake整体表现最优秀,3A5000还有一定的提升空间。浮点程序总体误预测率比定点程序小。影响程序性能的除了分支误预测率还有分支解决速度、错误路径指令的取消效率等,这些可以在分支吞吐率指标中有所体现。进一步针对具体程序分析相关指标差距的根源,有助于不断改进分支预测的性能。
分支预测对整体性能的影响还和分支指令的比例有关系。表12.22显示了SPEC CPU2006程序中分支指令的比例。可以看到,浮点程序中分支指令的比例明显少于定点程序。结合相对较低的误预测率,我们可以说分支预测对浮点程序的影响相对较小。分析整体影响时要综合考虑误预测率和分支比例。例如,从单个程序的数据异常看,浮点程序433.milc在3A5000上的误预测率高达6.58%,显著高于Zen1的误预测率0.4%和Skylake的0.23%。但是这个程序的分支比例非常少,在3A5000中只占1.2%,在Zen1和Skylake中占2.1%,因此它对总体性能的影响也比较小。
-
-
-表 12.22: SPEC CPU2006 分支指令所占百分比
+
+
+表 12.22: SPEC CPU2006 分支指令所占百分比
-
+SPEC CPU2006
loongson 3A5000
zen1 r3-1200
Skylake i3 9100f
400.perlbench
20.30%
22.00%
22.10%
401.bzip2
14.80%
15.80%
16.70%
403.gcc
19.30%
21.10%
25.70%
429.mcf
32.50%
22.80%
22.30%
445.gobmk
18.80%
18.90%
19.20%
456.hmmer
7.60%
8.50%
9.80%
458.sjeng
19.80%
20.00%
19.90%
462.libquantum
31.70%
29.50%
29.50%
464.h264ref
5.50%
6.90%
9.00%
471.omnetpp
25.10%
25.30%
24.30%
473.astar
23.20%
18.00%
18.10%
483.xalancbmk
30.10%
29.80%
29.90%
SPECint_2006
18.50%
18.40%
19.30%
410.bwaves
3.00%
2.40%
2.70%
416.gamess
6.60%
8.30%
8.60%
433.milc
1.20%
2.10%
2.10%
434.zeusmp
4.90%
1.60%
2.70%
435.gromacs
4.00%
3.00%
3.00%
436.cactusADM
0.20%
0.20%
0.60%
437.leslie3d
5.90%
4.70%
16.60%
444.namd
5.80%
5.40%
5.40%
447.dealII
17.50%
19.30%
19.60%
450.soplex
18.60%
19.30%
20.80%
453.povray
14.40%
16.80%
16.40%
454.calculix
5.60%
6.00%
6.70%
459.GemsFDTD
3.20%
3.10%
7.60%
465.tonto
7.00%
6.10%
10.00%
470.lbm
1.30%
1.40%
1.40%
481.wrf
7.80%
9.70%
11.40%
482.sphinx3
11.10%
12.00%
11.10%
SPECfp_2006
4.60%
4.50%
5.90%
+
+
+
12.4.3 基础性能参数
Perf利用事件采样获取许多有用的微观运行数据,还有一些工具则从软件角度测量系统的一些基础性能参数,两者可以互相补充。例如,对于存储子系统,可以用Perf来统计各级存储的Cache命中率,用LMbench测试各级存储访问延迟和带宽等,获得更全面的理解。本节展示LMbench的部分应用案例。
1)存储访问延迟
在处理器的性能指标中,各级Cache的访存延迟是一个重要的参数,其决定如果Cache访问命中或者失效,需要多少拍能回来,图12.3给出了3A5000和Zen1、Skylake的各级Cache和内存的访存延迟比较数据,其为基于跳步(stride)的访存测试结果。这个测试应用LMbench中的存储延迟测试工具,执行的命令为./lat_mem_rd 128M 4096,其中4096参数为跳步大小。其基本原理是,通过按给定间隔去循环读一定大小的内存区域,测量每个读平均的时间。如果区域大小小于L1 Cache大小,时间应该接近L1的访问延迟;如果大于L1小于L2,则接近L2访问延迟;依此类推。图中横坐标为访问的字节数,纵坐标为访存的拍数(cycles)。
-
-
-图 12.3: 基于跳步访问的3A5000和Zen1、Skylake各级延迟的比较(cycles)
+
+
+
+图 12.3: 基于跳步访问的3A5000和Zen1、Skylake各级延迟的比较(cycles)
+
从图中可以看出,L1 Cache的延迟,3A5000和Zen1、Skylake都为4拍;L2 Cache、L3 Cache和内存延迟,3A5000都优于Zen1;L2和L3 Cache的延迟,3A5000略优于Skylake,3A5000和Skylake内存访问延迟相似。
测试中还发现,通过变化stride参数的大小,当stride为64~1024字节,3A5000和Skylake处理器的L2、L3和内存延迟没有显著的突变(断层),而Zen1处理器在stride为512字节时,已经出现明显的断层,有可能Zen1的预取器覆盖范围较小。
-
-
-图 12.4: 基于随机访问的3A5000和Zen1、Skylake各级延迟的比较(cycles)
+
+
+
+图 12.4: 基于随机访问的3A5000和Zen1、Skylake各级延迟的比较(cycles)
+
规整的基于跳步的访存测试结果会受到处理器的硬件预取器的影响,如果采用随机访问预取器就比较难发挥作用。图12.4给出了3A5000和Zen1、Skylake的各级Cache和内存的随机访存延迟比较数据,执行的命令为./lat_mem_rd -t 128M 4096,其中4096为参考跳步的大小。基于随机访问的延迟比较接近相关存储的物理访问延迟。从图中可以看出,对于3A5000和Zen1、Skylake,L1 Cache的延迟都为4拍,L2 Cache的延迟分别为14拍、17拍和12拍,Skylake明显占优,L3 Cache的延迟分别为3845拍、3849和3848拍,内存延迟(随机访问)分别为228344拍、286298拍和164221拍。3A5000在L3 Cache的延迟上略微有优势,而Skylake在内存访问的延迟上有明显优势。从随机访问的内存延迟来看,当访问大小超过40MB时,3A5000的访存延迟会不断上升,一直到344拍,而Zen1和Skylake变化不大,基本维持在298拍和221拍左右,因此3A5000内存控制器可能存在改善空间。
表12.23给出了3A5000和AMD Zen1、Zen+以及Intel的Skylake处理器的L1、L2和L3 Cache以及内存访问延迟(cycles)的对比。2018年AMD公司推出Zen1的改进版Zen+,微结构基本没有变化,只是把L2、L3 Cache和内存的访问延迟降低了,其SPEC CPU性能提高了3%,因此,各级Cache和内存访问的延迟会直接影响处理器的性能。
-
-
-表 12.23: 3A5000和对比处理器的各级Cache和内存访问延迟数据
+
+
+表 12.23: 3A5000和对比处理器的各级Cache和内存访问延迟数据
-
+CPU型号
3A5000 2.5G
Zen1 r3 1200
Zen+ r3 3100
Skylake i3 9100f
一级Cache延迟
4拍
4拍
4拍
4拍
二级Cache延迟
14 拍
17拍
12拍
12拍
三级Cache延迟
38~45 拍
38~49拍
38~45拍
38~48拍
内存访问延迟
40拍+80ns
40拍+85ns
40拍+75ns
40拍+68ns
+
+
+
2)存储访问操作的并发性
图12.5给出了LMbench测试得到的访存操作的并发性,执行的命令为./par_mem。访存操作的并发性是各级Cache和内存所支持并发访问的能力。在LMbench中,访存操作并发性的测试是设计一个链表,不断地遍历访问下一个链表中的元素,链表所跳的距离和需要测量的Cache容量相关,在一段时间能并发的发起对链表的追逐操作,也就是同时很多链表在遍历,如果发现这一段时间内能同时完成N个链表的追逐操作,就认为访存的并发操作是N。
-
-
-图 12.5: 访存操作的并发性
+
+
+
+图 12.5: 访存操作的并发性
+
从图中可以看出,3A5000的访存并发性和AMD的Zen1以及Intel的Skylake处理器基本相当,在L3 Cache大小范围时略有优势。
3)功能部件延迟
表12.24列出了三款处理器的功能部件操作延迟数据,使用的命令是./lat_ops。
-
-
-表 12.24: 3A5000、Zen1和Skylake的功能部件操作延迟
+
+
+表 12.24: 3A5000、Zen1和Skylake的功能部件操作延迟
-
+lat_ops延迟(ns)
loongson 3A5000
zen1 r3-1200
skylake i3 9100f
integer bit
0.27
0.29
0.27
integer add
0.00
0.00
0.00
integer mul
0.02
0.02
1.24
integer div
6.21
8.03
10.86
integer mod
3.60
5.61
11.32
int64 bit
0.27
0.29
0.27
int64 add
0.00
0.00
0.00
int64 mul
0.02
0.02
1.22
int64 div
7.60
10.63
17.10
int64 mod
3.34
5.34
16.55
float add
2.00
1.20
1.60
float mul
2.00
1.20
1.61
float div
10.82
4.04
4.58
double add
2.00
1.20
1.60
double mul
2.00
1.60
1.60
double div
9.22
5.25
5.78
float bogomflops
5.04
1.53
1.20
double bogomflops
9.62
1.81
1.61
+
+
+
从测试数据来看,3A5000的定点操作延迟控制较好,多数优于其他两款处理器,但是浮点操作延迟则多数偏长,这也可能是其SPEC浮点程序性能落后的部分原因。当然,这些测试本身是否足够合理需要进一步分析相应的测试代码,不能无条件采用。LMbench的lat_ops说明文档中明确表示,这个测试程序是实验性的,有时(甚至经常)会给出误导的结论。性能分析工作中常见的误区之一就是被不准确甚至错误的数据误导,得出错误的结论。因此使用各种工具时,应该尽可能了解目标系统和工具的工作原理,对数据的合理性进行必要的判断。
4)STREAM带宽
LMbench包含了STREAM带宽测试工具,可以用来测试可持续的内存访问带宽情况。图表12.25列出了三款处理器的STREAM带宽数据,其中STREAM数组大小设置为1亿个元素,采用OpenMP版本同时运行四个线程来测试满载带宽;相应测试平台均为CPU的两个内存控制器各接一根内存条,3A5000和Zen1用DDR4 3200内存条,Skylake用DDR4 2400内存条(它最高只支持这个规格)。
-
-
-表 12.25: 3A5000、Zen1和Skylake的STREAM带宽
+
+
+表 12.25: 3A5000、Zen1和Skylake的STREAM带宽
-
+STREAM四核(openMP)
3A5000
Zen1
Skylake
Copy
23,861
39,896
26,983
Scale
22,347
25,073
19,111
Add
19,323
29,768
21,516
Triad
21,044
29,147
21,491
+
+
+
从测试数据来看,3A5000的定点操作延迟控制较好,多数优于其他两款处理器,但是浮点操作延迟则多数偏长,这也可能是其SPEC浮点程序性能落后的部分原因。当然,这些测试本身是否足够合理需要进一步分析相应的测试代码,不能无条件采用。LMbench的lat_ops说明文档中明确表示,这个测试程序是实验性的,有时(甚至经常)会给出误导的结论。性能分析工作中常见的误区之一就是被不准确甚至错误的数据误导,得出错误的结论。因此使用各种工具时,应该尽可能了解目标系统和工具的工作原理,对数据的合理性进行必要的判断。
从数据可以看到,虽然硬件上3A5000和Zen1都实现了DDR4 3200,但3A5000的实测可持续带宽还是有一定差距。用户程序看到的内存带宽不仅仅和内存的物理频率有关系,也和处理器内部的各种访存队列、内存控制器的调度策略、预取器和内存时序参数设置等相关,需要进行更多分析来定位具体的瓶颈点。像STREAM这样的软件测试工具,能够更好地反映某个子系统的综合能力,因而被广泛采用。
diff --git a/计算机组成原理和结构.html b/计算机组成原理和结构.html
index 19fd3da..e635ac8 100644
--- a/计算机组成原理和结构.html
+++ b/计算机组成原理和结构.html
@@ -216,8 +216,9 @@ div.csl-indent {
- 2.5 RISC指令集比较
- 2.6 C语言的机器表示
@@ -290,6 +291,7 @@ div.csl-indent {
- 5.3.2 CPU-北桥-南桥三片结构
- 5.3.3 CPU-弱北桥-南桥三片结构
- 5.3.4 CPU-南桥两片结构
+- 5.3.5 SoC单片结构
- 5.4 处理器和IO设备间的通信
@@ -423,16 +425,17 @@ div.csl-indent {
- 11.2.3 Cache一致性协议
- 11.3 多核处理器的互连结构
-- 11.4 典型多核处理器
+
- 11.4 多核处理器的同步机制
+- 11.5 典型多核处理器
-- 11.5 本章小结
-- 11.6 习题
+- 11.6 本章小结
+- 11.7 习题
- VI 系统性能评价与分析
- 12 计算机系统性能评价与性能分析
@@ -498,9 +501,11 @@ div.csl-indent {
5.1 冯·诺依曼结构
现代计算机都采用存储程序结构,又称为冯·诺依曼结构,是1945年匈牙利籍数学家冯·诺依曼受宾夕法尼亚大学研制的ENIAC计算机结构的启发提出的,是世界上第一个完整的计算机体系结构。
冯·诺依曼结构的主要特点是:①计算机由存储器、运算器、控制器、输入设备和输出设备五部分组成,其中运算器和控制器合称为中央处理器(Central Processing Processor,简称CPU)。②存储器是按地址访问的线性编址的一维结构,每个单元的位数固定。③采用存储程序方式,即指令和数据不加区别混合存储在同一个存储器中。④控制器通过执行指令发出控制信号控制计算机的操作。指令在存储器中按其执行顺序存放,由指令计数器指明要执行的指令所在的单元地址。指令计数器一般按顺序递增,但执行顺序可按运算结果或当时的外界条件而改变。⑤以运算器为中心,输入输出设备与存储器之间的数据传送都经过运算器。冯·诺依曼计算机的工作原理如图5.1所示。
-
-
-图 5.1: 冯·诺依曼计算机体系结构
+
+
+
+图 5.1: 冯·诺依曼计算机体系结构
+
随着技术的进步,冯·诺依曼结构得到了持续的改进,主要包括以下几个方面:①由以运算器为中心改进为以存储器为中心。使数据的流向更加合理,从而使运算器、存储器和输入输出设备能够并行工作。②由单一的集中控制改进为分散控制。计算机发展初期,工作速度很低,运算器、存储器、控制器和输入输出设备可以在同一个时钟信号的控制下同步工作。现在运算器、内存与输入输出设备的速度差异很大,需要采用异步方式分散控制。③从基于串行算法改进为适应并行算法。出现了流水线处理器、超标量处理器、向量处理器、多核处理器、对称多处理器(Symmetric Multiprocessor,简称SMP)、大规模并行处理机(Massively Parallel Processing,简称MPP)和机群系统等。④出现为适应特殊需要的专用计算机,如图形处理器(Graphic Processing Unit,简称GPU)、数字信号处理器(Digital Signal Processor,简称DSP)等。⑤在非冯·诺依曼计算机的研究方面也取得一些成果,如依靠数据驱动的数据流计算机、图归约计算机等。
虽然经过了长期的发展,现代计算机系统占据主要地位的仍然是以存储程序和指令驱动执行为主要特点的冯·诺依曼结构。
@@ -515,11 +520,94 @@ div.csl-indent {
运算器支持的运算类型经历了从简单到复杂的过程。最初的运算器只有简单的定点加减和基本逻辑运算,复杂运算如乘除通过加减、移位指令构成的数学库完成;后来逐渐出现硬件定点乘法器和除法器。在早期的微处理器中,浮点运算器以协处理器的形式出现在计算机中(如Intel 8087协处理器),包含二进制浮点数的加、减、乘、除等运算,现代的通用微处理器则普遍包含完整的浮点运算部件。20世纪90年代开始,微处理器中出现了单指令多数据(Single Instruction Multiple Data,简称SIMD)的向量运算器,部分处理器还实现了超越函数硬件运算单元,如sin、cos、exp、log等。部分用于银行业务处理的计算机(如IBM Power系列)还实现了十进制定、浮点数的运算器。
随着晶体管集成度的不断提升,处理器中所集成的运算器的数量也持续增加,通常将具有相近属性的一类运算组织在一起构成一个运算单元。不同的处理器有不同的运算单元组织,有的倾向于每个单元大而全,有的倾向于每个单元的功能相对单一。处理器中包含的运算单元数目也逐渐增加,从早期的单个运算单元逐渐增加到多个运算单元。由于运算单元都需要从寄存器中读取操作数,并把结果写回寄存器,因此处理器中运算单元的个数主要受限于寄存器堆读写端口个数。运算单元一般按照定点、浮点、访存、向量等大类来组织,也有混合的,如SIMD部件既能做定点也能做浮点运算,定点部件也可以做访存地址计算等。
表5.1给出了几种经典处理器的运算器结构。其中Alpha 21264、MIPS R10000、HP PA8700、Ultra Sparc III、Power 4是20世纪90年代RISC处理器鼎盛时期经典的微处理器,而Intel Skylake、AMD Zen、Power 8、龙芯3A5000则是最新处理器。
-
-
-表 5.1: 经典处理器的运算器结构
+
+
+表 5.1: 经典处理器的运算器结构
-
+处理器
寄存器
运算部件
Alpha 21264
Int regfile (80 4r6w)
+
FP regfile (72 4r4w)
arith./logic unit; shift unit;
+
mult unit; add/logic unit;
+
shift unit; MVI/PLZ unit;
+
arith/logic; arith/logic unit;
+
FP add unit; FP mult unit;
+
FP div/sqrt unit
MIPS R10000
Int regfile (64 7r3w)
+
FP regfile (64 5r3w)
arith./logic unit; shift unit;
+
arith./logic unit; mult/div unit;
+
FP add/sub unit;
+
FP compae/coversion unit;
+
FP mult unit; FP div/sqrt unit
HP PA8700
Intarchregfile (32 8r4w)
+
Int renregfile (56 9r4w)
+
FP archregfile (32 8r4w)
+
FP renregfile (56 9r4w)
2 arithlogic units;
+
2 shift merge units;
+
2 FP MAC units;
+
2 FP div/sqrt units
Ultra Sparc III
Int regfile (144 7r3w)
+
FP regfile (32 5r4w)
2 arith units; logic unit; shift unit;
+
FP adder unit; graphic unit;
+
FP div/sqrt unit; FP mult unit; graphic unit
Power4
GPRS (80)
+
FPRS (72)
2 fixed-point units;
+
2 floating-point units
Zen
Int regfile (168)
+
FP regfile (160)
4 floating-point/vector units;
+
4 fixed-point units
Skylake
Int regfile (180)
+
FP regfile (168)
4 fixed-point units;
+
3 floating-point/vector units
Power8
GPRS (2×124)
+
VSRS (2×144)
2 fixed-point units;
+
4 floating-point units;
+
2 vector units;
+
decimal floating-point unit;
+
crypto unit
龙芯3A5000
Int regfile (128 12r8w)
+
FP regfile (128 8r6w)
4 fixed-point units;
+
2 floating-point/vector units
+
+
+
5.2.2 控制器
@@ -545,9 +633,11 @@ div.csl-indent {
3)动态随机访问存储器(DRAM)。属于易失性存储器(断电后数据丢失)。特点是存储密度较高(存储一位数据只需一个晶体管),需要周期性刷新,访问速度较快。其访问速度一般在几十纳秒级。
4)静态随机访问存储器(SRAM)。属于易失性存储器(断电后数据丢失)。存储密度不如DRAM高(SRAM存储一位数据需要4-8个晶体管),不用周期性刷新,但访问速度比DRAM快,可以达到纳秒级,小容量时能够和处理器核工作在相同的时钟频率。
现代计算机中把上述不同的存储介质组成存储层次,以在成本合适的情况下降低存储访问延迟,如图5.2中所示,越往上的层级,速度越快,但成本越高,容量越小;越往下的层级,速度越慢,但成本越低,容量越大。图5.2所示存储层次中的寄存器和主存储器直接由指令访问,Cache缓存主存储器的部分内容;而非易失存储器既是辅助存储器,又是输入输出设备,非易失存储器的内容由操作系统负责调入调出主存储器。
-
-
-图 5.2: 存储层次
+
+
+
+图 5.2: 存储层次
+
存储层次的有效性,依赖于程序的访存局部性原理,包含两个方面:一是时间局部性,指的是如果一个数据被访问,那么在短时间内很有可能被再次访问;二是空间局部性,指的是如果一个数据被访问,那么它的邻近数据也很有可能被访问。利用局部性原理,可以把程序近期可能用到的数据存放在靠上的层次,把近期内不会用到的数据存放在靠下的层次。通过恰当地控制数据在层次间的移动,使处理器需要访问的数据尽可能地出现在靠近处理器的存储层次,可以大大提高处理器获得数据的速度,从而近似达到用最快的存储器构建一个容量很大的单级存储的效果。现代计算机一般使用多端口寄存器堆实现寄存器,使用SRAM来构建片上的高速缓存(Cache),使用DRAM来构建程序的主存储器(也称为主存、内存),使用磁盘或闪存来构建大容量的存储器。
1.高速缓存
@@ -558,14 +648,21 @@ div.csl-indent {
其中HitTime表示高速缓存命中时的访问延迟,MissRate表示高速缓存失效率,MissPenalty表示高速缓存失效时额外的访问延迟。例如,在某计算机系统中HitTime=1, MissRate=5%, MissPenalty=100,则AMAT=1+5=6。
2.内存
主存储器又称为内存。内存的读写速度对计算机的整体性能影响重大。为了提升处理器的访存性能,现代通用处理器都将内存控制器与CPU集成在同一芯片内,以减小平均访存延迟。
-现代计算机的内存一般都采用同步动态随机存储器(SDRAM)实现。DRAM的一个单元由MOS管T和电容C(存储单元)组成,如图5.3所示。电容C存储的电位决定存储单元的逻辑值。单元中的字线根据读写地址译码得到,连接同一字的若干位;单元中的位线把若干字的同一位链接在一起。进行读操作时,先把位线预充到Vref=VCC/2,然后字线打开T管,C引起差分位线微小的电位差,感应放大器读出,读出后C中的电位被破坏,需要把读出值重新写入C。进行写操作时,先把位线预充成要写的值,然后打开字线,把位线的值写入C。C中的电容可能会漏掉,因此DRAM需要周期刷新,刷新可以通过读操作进行,一般每行几十微秒刷新一次。
+现代计算机的内存一般都采用同步动态随机存储器(SDRAM)实现。DRAM的一个单元由MOS管T和电容C(存储单元)组成,如图5.3所示。电容C存储的电位决定存储单元的逻辑值。单元中的字线根据读写地址译码得到,连接同一字的若干位;单元中的位线把若干字的同一位链接在一起。进行读操作时,先把位线预充到Vref=VCC/2,然后字线打开T管,C引起差分位线微小的电位差,感应放大器读出,读出后C中的电位被破坏,需要把读出值重新写入C。进行写操作时,先把位线预充成要写的值,然后打开字线,把位线的值写入C。C中的电容可能会漏掉,因此DRAM需要周期刷新,刷新可以通过读操作进行,一般每行几十微秒刷新一次。
"
-
+
+
+
+图 5.3: DRAM的单元读写原理
+
+
SDRAM芯片一般采用行列地址线复用技术,对SDRAM进行读写时,需要先发送行地址打开一行,再发送列地址读写需要访问的存储单元。为了提高访问的并发度,SDRAM芯片一般包含多个Bank(存储块),这些Bank可以并行操作。图5.4显示了一个DDR2 SDRAM x8芯片的内部结构图。可以看到,该SDRAM内部包含了8个Bank,每个Bank对应一个存储阵列和一组感应放大器,所有的Bank共用读锁存(Read Latch)和写FIFO。
对SDRAM进行写操作后,由于必须等到写数据从IO引脚传送到对应Bank的感应放大器后,才能进行后续的预充电操作(针对相同Bank)或者读操作(针对所有Bank),因此写操作会给后续的其他操作带来较大的延迟,但连续的写操作却可以流水执行。为了降低写操作带来的开销,内存控制器往往将多个写操作聚集在一起连续发送,以分摊单个写操作的开销。
-
-
-图 5.4: SDRAM的功能结构图
+
+
+
+图 5.4: SDRAM的功能结构图
+
影响SDRAM芯片读写速度的因素有两个:行缓冲局部性(Row Buffer Locality,简称RBL)和Bank级并行度(Bank Level Parallelism,简称BLP)。
1)行缓冲局部性。如图5.4所示,SDRAM芯片的一行数据在从存储体中读出后,存储体中的值被破坏,保存在对应的一组感应放大器中,这组感应放大器也被称为行缓冲。如果下一个访存请求访问同一行的数据(称为命中行缓冲),可以直接从该感应放大器中读出,而不需要重新访问存储体内部,可以大大降低SDRAM的访问延迟。当然,在行缓冲不命中的时候,就需要首先将行缓冲中的数据写回存储体,再将下一行读出到行缓冲中进行访问。由此,对DRAM可以采用关行(Close Page)和开行(Open Page)两种策略。使用关行策略时,每次读写完后先把行缓冲的内容写入存储体,才能进行下一次读写,每次读写的延迟是确定的。使用开行策略时,每次读写完后不把行缓冲的内容写入存储体,如果下一次读写时所读写的数据在行缓冲中(称为行命中),可以直接对行缓冲进行读写即可,延迟最短;如果下一次读写时所读写的数据不在行缓冲中,则需要先将行缓冲中的数据写回对应的行,再将新地址的数据读入行缓冲,再进行读写,延迟最长。因此,如果内存访问的局部性好,可以采用开行策略;如果内存访问的局部性不好,则可以采用关行策略。内存控制器可以通过对多个访存请求进行调度,尽量把对同一行的访问组合在一起,以增加内存访问的局部性。
@@ -596,9 +693,11 @@ div.csl-indent {
2.硬盘
计算机除了需要内存存放程序的中间数据外,还需要具有永久记忆功能的存储体来存放需要较长时间保存的信息。比如操作系统的内核代码、文件系统、应用程序和用户的文件数据等。该存储器除了容量必须足够大之外,价格还要足够便宜,同时速度还不能太慢。在计算机的发展历史上,磁性存储材料正好满足了以上要求。磁性材料具有断电记忆功能,可以长时间保存数据;磁性材料的存储密度高,可以搭建大容量存储系统;同时,磁性材料的成本很低。
人们目前使用的硬盘就是一种磁性存储介质。硬盘的构造原理为:将磁性材料覆盖在圆形碟片(或者说盘片)上,通过一个读写头(磁头)悬浮在碟片表面来感知存储的数据。通过碟片的旋转和磁头的径向移动来读写碟片上任意位置的数据。碟片被划分为多个环形的轨道(称为磁道,Track)来保存数据,每个磁道又被分为多个等密度(等密度数据)的弧形扇区(Sector)作为存储的基本单元。磁盘的内部构造如图5.5所示。硬盘在工作时,盘片是一直旋转的,当想要读取某个扇区的数据时,首先要将读写头移动到该扇区所在的磁道上,当想要读写的扇区旋转到读写头下时,读写头开始读写数据。
-
-
-图 5.5: 磁盘的内部结构图
+
+
+
+图 5.5: 磁盘的内部结构图
+
衡量磁盘性能的指标包括响应时间和吞吐量,也就是延迟和带宽。磁头移动到目标磁道的时间称为寻道时间。当磁头移动到目标磁道后,需要等待目标扇区旋转到磁头下面,这段时间称为旋转时间。旋转时间与盘片的旋转速度有关,磁盘的旋转速度用RPM(Rotation Per Minute,转/分)来表示,我们常说的5400转、7200转,就是指磁盘的旋转速度。扇区旋转到目标位置后,传输这个扇区的数据同样需要时间,称为传输时间。传输时间是扇区大小、旋转速度和磁道记录密度的函数。
磁盘是由磁盘控制器控制的。磁盘控制器控制磁头的移动、接触和分离以及磁盘和内存之间的数据传输。另外,通过IO操作访问磁盘控制器又会引入新的时间。现在的磁盘内部一般都会包含一个数据缓冲,读写磁盘时,如果访问的数据正好在缓冲中命中,则不需要访问磁盘扇区。还有,当有多个命令读写磁盘时,还需要考虑排队延迟。因此,磁盘的访问速度计算起来相当复杂。一般来说,磁盘的平均存取时间在几个毫秒的量级。
@@ -620,9 +719,11 @@ div.csl-indent {
5.3.1 CPU-GPU-北桥-南桥四片结构
现代计算机的一种早期结构是CPU-GPU-北桥-南桥结构。在该结构中,计算机系统包含四个主要芯片,其中CPU(处理器)芯片、北桥芯片和南桥芯片一般是直接以芯片的形式安装或焊接在计算机主板上,而GPU则以显卡的形式安装在计算机主板的插槽上。
在CPU-GPU-北桥-南桥四片结构中,计算机的各个部件根据速度快慢以及与处理器交换数据的频繁程度被安排在北桥和南桥中。CPU通过处理器总线(也称系统总线)和北桥直接相连,北桥再通过南北桥总线和南桥相连,GPU一般以显卡的形式连接北桥。内存控制器集成在北桥芯片中,硬盘接口、USB接口、网络接口、音频接口以及鼠标、键盘等接口放在南桥芯片中。此外,在北桥上还会提供各种扩展接口用于其他功能卡的连接。采用该结构的微机系统如图5.6所示。
-
-
-图 5.6: CPU-GPU-北桥-南桥结构
+
+
+
+图 5.6: CPU-GPU-北桥-南桥结构
+
与英特尔奔腾处理器搭配的430HX芯片组就采用了这样的四片结构。其北桥芯片使用82439HX,南桥芯片采用82371SB,通过PCI总线扩展外接显卡,与处理器组成四片结构,作为计算机系统的主要部分。
@@ -631,9 +732,11 @@ div.csl-indent {
现代计算机的一种典型结构是CPU-北桥-南桥结构。在该结构中,系统包含三个主要芯片,分别为CPU芯片、北桥芯片和南桥芯片。三片结构与四片结构最大的区别是,前者GPU功能被集成到北桥,即一般所说的集成显卡。
在CPU-北桥-南桥三片结构中,CPU通过处理器总线和北桥直接相连,北桥再通过南北桥总线和南桥相连。内存控制器、显示功能以及高速IO接口(如PCIE等)集成在北桥芯片中,硬盘接口、USB接口、网络接口、音频接口以及鼠标、键盘等接口部件放在南桥芯片中。随着计算机技术的发展,更多的高速接口被引入计算机体系结构中,在北桥上集成的IO接口的速率也不断提升。
采用该结构的微机系统如图5.7所示。
-
-
-图 5.7: CPU -北桥 -南桥结构
+
+
+
+图 5.7: CPU -北桥 -南桥结构
+
英特尔845G芯片组就采用类似的三片结构。其北桥芯片使用82845G,集成显示接口,南桥芯片采用82801DB,与处理器组成三片结构,作为计算机系统的主要部分。
@@ -643,24 +746,35 @@ div.csl-indent {
因此,对计算机系统性能影响显著的内存控制器开始被集成到CPU芯片中,从而大幅降低了内存访问延迟,提升了内存访问带宽,这在一定程度上缓解了存储墙问题。
于是,北桥的功能被弱化,主要集成了GPU、显示接口、高速IO接口(例如PCIE接口等)。
采用该结构的微机系统如图5.8所示。
-
-
-图 5.8: CPU -弱北桥 -南桥结构
+
+
+
+图 5.8: CPU -弱北桥 -南桥结构
+
相比英特尔,AMD的处理器最早将内存控制器集成到处理器芯片中,780E芯片组就采用上述三片结构,北桥芯片使用RS780E,集成HD3200 GPU,南桥芯片使用SB710,与处理器组成三片结构,作为计算机系统的主要部分。
5.3.4 CPU-南桥两片结构
在计算机系统不断发展的过程中,图形处理器性能也在飞速发展,其在系统中的作用也不断被开发出来。除了图形加速以外,对于一些科学计算类的应用,或者是一些特定的算法加速程序,图形处理器发挥着越来越大的作用,成为特定的运算加速器,其与中央处理器之间的数据共享也越来越频繁,联系越来越密切。
-随着芯片集成度的进一步提高,图形处理器也开始被集成到CPU芯片中,于是,北桥存在的必要性就进一步降低,开始和南桥合二为一,形成CPU-南桥结构,如图5.9所示。
+随着芯片集成度的进一步提高,图形处理器也开始被集成到CPU芯片中,于是,北桥存在的必要性就进一步降低,开始和南桥合二为一,形成CPU-南桥结构,如图5.9所示。
在这个结构中,CPU芯片集成处理器核、内存控制器和GPU等主要部件,对外提供显示接口、内存接口等,并通过处理器总线和南桥相连。南桥芯片则包含硬盘、USB、网络控制器以及PCIE/PCI、LPC等总线接口。由于GPU和CPU都需要大量访问内存,会带来一些访存冲突,而且相对来说,GPU对于实时性的要求更高,即访存优先级会更高一些,这在一定程度上会影响CPU的性能。实际上,处理器中集成的GPU性能相比独立显卡中的GPU性能会稍弱。
当然,也有一些两片结构是将GPU集成在南桥芯片中。这样在南桥上可以实现独立的显存供GPU使用,这在某些条件下更有利于GPU性能的发挥,且CPU升级时带来的开销会更小。
-
-### SoC单片结构
+
+
+
+图 5.9: CPU - 南桥结构
+
+
+
+
+5.3.5 SoC单片结构
片上系统(System on Chip,简称SoC)是一种单片计算机系统解决方案,它在单个芯片上集成了处理器、内存控制器、GPU以及硬盘、USB、网络等IO接口,使得用户搭建计算机系统时只需要使用单个主要芯片即可,如图5.10所示。
-
-
-图 5.10: SOC单片结构
+
+
+
+图 5.10: SOC单片结构
+
目前SoC主要应用于移动处理器和工业控制领域,相比上述几种多片结构,单片SoC结构的集成度更高,功耗控制方法更加灵活,有利于系统的小型化和低功耗设计。但也因为全系统都在一个芯片上实现,导致系统的扩展性没有多片结构好,升级的开销也更大。随着技术的发展,封装基板上的互连技术不断发展和成熟。越来越多的处理器利用多片封装技术在单个芯片上集成多个硅片,以扩展芯片的计算能力或IO能力。例如AMD Ryzen系列处理器通过在封装上集成多个处理器硅片和IO硅片,以提供针对不同应用领域的计算能力。龙芯3C5000L处理器则通过在封装上集成4个4核龙芯3A5000硅片来实现单片16核结构。
目前,主流商用处理器中面向中高端领域的处理器普遍采用两片结构,而面向中低端及嵌入式领域的处理器普遍采用单片结构。SoC单片结构最常见的是在手机等移动设备中。
@@ -708,11 +822,61 @@ div.csl-indent {
DMA方式对于存在大量数据传输的高速设备是一个很好的选择,硬盘、网络、显示等设备普遍都采用DMA方式。一个计算机系统中通常包含多个DMA控制器,比如有特定设备专用的SATA接口DMA控制器、USB接口DMA控制器等,也有通用的DMA控制器用于可编程的源地址与目标地址之间的数据传输。
DMA控制器的功能可以很简单,也可以很复杂。例如,DMA控制器可以仅仅支持对一段连续地址空间的读写,也可以支持对多段地址空间的读写以及执行其他的IO操作。不同的IO设备的DMA行为各不相同,因此现代的IO控制器大多会实现专用的DMA控制器用于自身的数据传输。
表5.2举例说明了PIO和DMA两种数据传输方式的不同。
-
-
-表 5.2: PIO和DMA两种数据传输方式
+
+
+表 5.2: PIO和DMA两种数据传输方式
-
+PIO方式
DMA方式
键盘输入
网卡收包
敲击键盘
接收端收到网络包
键盘输入被记录在PS/2控制器内
网卡将收到的网络包写入内存中预先分配好的内存中
PS/2控制器向处理器发送中断
网卡向处理器发送中断
CPU查询中断源,发现键盘中断
CPU查询中断源,发现网卡接收中断
CPU从PS/2控制器内读回键盘值
CPU从内存中读到网络包,并进行处理,初始化新的接收缓冲供网卡使用
CPU清中断
CPU清中断
+
+
+
从
上面两个例子中可以看到,PIO方式和DMA方式处理的流程一致,区别在于:首先键盘的数据是被记录在IO设备本身的,而网卡的数据则直接由网卡写入内存之中;其次CPU处理时,对键盘是直接从IO寄存器读数据,而对网卡则直接从内存读数据。
看起来似乎差别不大。但需要考虑的是,IO访问相比内存访问慢很多,而且对于内存访问,CPU可以通过Cache、预取等方式进行加速,IO访问则缺少这种有效的优化方式。在上面的例子中,如果网卡采用PIO的方式使用CPU,对网卡的包一个字一个字地进行读访问,效率将非常低下。而对于键盘来说,一次输入仅仅只有8位数据,而且相比处理器的处理速度,键盘输入的速度相当低,采用PIO的处理方式能够很简单地完成数据输入任务。
@@ -720,18 +884,22 @@ div.csl-indent {
5.4.4 龙芯3A3000+7A1000桥片系统中的CPU、GPU、DC通信
下面以龙芯3A3000+7A1000桥片中CPU、GPU、DC间的同步与通信为例说明处理器与IO间的通信。如图5.11所示,龙芯3A3000处理器和龙芯7A1000桥片通过HyperTransport总线相连,7A1000桥片中集成GPU、DC(显示控制器)以及专供GPU和DC使用的显存控制器。CPU可以通过PIO方式读写GPU中的控制寄存器、DC中的控制寄存器以及显存;GPU和DC可以通过DMA方式读写内存,GPU和DC还可以读写显存。
-
-
-图 5.11: 龙芯3A3000+7A1000两片方案
+
+
+
+图 5.11: 龙芯3A3000+7A1000两片方案
+
CPU或GPU周期性地把要显示的数据写入帧缓存(Frame Buffer),DC根据帧缓存的内容进行显示。帧缓存可以分配在内存中,GPU和DC通过DMA方式访问内存中的帧缓存;在独立显存的情况下,帧缓存分配在独立显存中,CPU直接把要显示的数据写入帧缓存,或者GPU通过DMA方式从内存中读取数据并把计算结果写入帧缓存,DC直接读取帧缓存的内容进行显示。根据是否由GPU完成图形计算以及帧缓存是否分配在内存中,常见的显示模式有以下四种。
模式一:不使用GPU,CPU与DC共享内存。不使用桥片上的显存,而在内存中分配一个区域专供显示使用,这个区域称之为帧缓存(framebuffer)。需要显示时,CPU通过正常内存访问将需要显示的内容写入内存中的帧缓存,然后通过PIO方式读写DC中的控制寄存器启动DMA,DC通过DMA操作读内存中的帧缓存并进行显示,如图5.12a所示。
模式二:不使用GPU,DC使用独立显存。DC使用桥片上的显存,这个区域称之为帧缓存。需要显示时,CPU将需要显示的内容从内存读出,再通过PIO方式写入独立显存上的帧缓存,然后通过PIO操作读写DC中的控制寄存器启动DMA,DC读显存上的帧缓存并进行显示,如图5.12b所示。
模式三:CPU与GPU/DC共享内存。需要显示时,CPU在内存中分配GPU使用的空间,并将相关数据填入,然后CPU通过PIO读写GPU中的控制寄存器启动DMA操作,GPU通过DMA读内存并将计算结果通过DMA写入内存中的帧缓存,CPU通过PIO方式读写DC中的控制寄存器启动DMA,DC通过DMA方式读内存中的帧缓存并完成显示,如图5.12c所示。
模式四:GPU/DC使用独立显存。需要显示时,CPU在内存中分配GPU使用的空间,并将相关数据填入,然后CPU通过PIO读写GPU中的控制寄存器启动DMA操作,GPU通过DMA读内存并将计算结果写入显存中的帧缓存,DC读显存中的帧缓存并完成显示,如图5.12d所示。
-
-
-图 5.12: 3A3000+7A1000的不同显示方式
+
+
+
+图 5.12: 3A3000+7A1000的不同显示方式
+
@@ -742,7 +910,7 @@ div.csl-indent {
5.6 习题
-
+
查阅资料,比较Skylake处理器和Zen处理器的运算器结构。
说明ROB、保留站(发射队列)、重命名寄存器在指令流水线中的作用,并查阅资料,比较Skylake处理器和Zen处理器的ROB、发射队列、重命名寄存器项数。
假设A处理器有两级Cache,一级Cache大小为32KB,命中率为95%,命中延迟为1拍;二级Cache大小为1MB,命中率为80%,命中延迟为30拍,失效延迟为150拍。B处理器有三级Cache,一级Cache大小为32KB,命中率为95%,命中延迟为1拍;二级Cache大小为256KB,命中率为75%,命中延迟为20拍;三级Cache大小为4MB,命中率为80%,命中延迟为50拍,失效延迟为150拍。比较两款处理器的平均访问延迟。
diff --git a/软硬件协同.html b/软硬件协同.html
index 69eef64..0edb1ea 100644
--- a/软硬件协同.html
+++ b/软硬件协同.html
@@ -216,8 +216,9 @@ div.csl-indent {
- 2.5 RISC指令集比较
- 2.6 C语言的机器表示
@@ -290,6 +291,7 @@ div.csl-indent {
- 5.3.2 CPU-北桥-南桥三片结构
- 5.3.3 CPU-弱北桥-南桥三片结构
- 5.3.4 CPU-南桥两片结构
+- 5.3.5 SoC单片结构
- 5.4 处理器和IO设备间的通信
@@ -423,16 +425,17 @@ div.csl-indent {
- 11.2.3 Cache一致性协议
- 11.3 多核处理器的互连结构
-- 11.4 典型多核处理器
+
- 11.4 多核处理器的同步机制
+- 11.5 典型多核处理器
-- 11.5 本章小结
-- 11.6 习题
+- 11.6 本章小结
+- 11.7 习题
- VI 系统性能评价与分析
- 12 计算机系统性能评价与性能分析
@@ -519,11 +522,61 @@ div.csl-indent {
2)N64。在64位处理器编程中使用的新的正式ABI,指针和long型整数的宽度扩展为64位,并改变了寄存器使用的约定和参数传递的方式。
3)N32。在64位处理器上执行的32位程序,与N64的区别在于指针和long型整数的宽度为32位。
表4.1给出了MIPS O32和N32/N64对整数(或称为定点)通用寄存器的命名和使用约定。
-
-
-表 4.1: MIPS整数通用寄存器约定
+
+
+表 4.1: MIPS整数通用寄存器约定
-
+寄存器编号
O32助记符
N32/N64助记符
使用约定
0
zero
总是为0
1
at
汇编暂存器
2~3
v0,v1
子程序返回值
4~7
a0~a3
子程序的前几个参数
8~11
t0~t3
a4~a7
N32作为参数,O32作为不需保存的暂存器
12~15
t4~t7
t0~t3
不需保存的暂存器,但N32和O32命名不同
16~23
s0~s7
寄存器变量,过程调用时需要存储和恢复
24~25
t8,t9
暂存器
26~27
k0,k1
为异常处理保留
28
gp
全局指针
29
sp
栈指针
30
s8/fp
寄存器变量,或作为帧指针
31
ra
子程序返回地址
+
+
+
这三个ABI中,O32用一种寄存器约定,N32/N64用另一种。可以看到,两种寄存器约定的大部分内容是相同的,主要差别在于O32只用了四个寄存器作为参数传递寄存器,而N32/N64则用了八个,相应地减少了暂存器。原因是现代程序越来越复杂,很多函数的参数超过四个,在O32中需要借助内存来传递多出的参数,N32/N64的约定有助于提升性能。对参数少于八个的函数,剩余的参数寄存器仍然可以当作暂存器使用,不会浪费。为了和普通变量名区分,这些助记符在汇编源代码中会加’$’前缀,例如$sp或者$r29表示29号寄存器。但在一些源代码(如Linux内核源代码)中也可能会看到直接使用不加$前缀的助记符的情况,这是因为相关头文件用宏定义了这个名字,如#define a0 $r4。
LoongArch定义了三个ABI:指针和数据都是64位的LP64,指针32位、数据64位的LPX32,指针和数据都是32位的LP32。但它们的寄存器约定都是一致的。对比表4.1和表4.2,我们可以看到LoongArch的约定比MIPS要更规整和简洁些,主要有如下差别:
@@ -533,11 +586,61 @@ div.csl-indent {
复用参数寄存器和返回值寄存器,参数寄存器$a0/$a1也被用作返回值寄存器。这也是现代指令系统比较常见的做法,它进一步增加了通用暂存器的数量。
增加了线程指针寄存器$tp,用于高效支持多线程实现。$tp总是指向当前线程的TLS(Thread Local Storage)区域。
-
-
-表 4.2: LoongArch整数通用寄存器约定
+
+
+表 4.2: LoongArch整数通用寄存器约定
-
+寄存器编号
助记符
使用约定
0
zero
总是为0
1
ra
子程序返回地址
2
tp
Thread Pointer,指向线程私有存储区
3
sp
栈指针
4~11
a0~a7
子程序的前八个参数
4~5
v0~v1
v0/v1是a0/a1的别名,用于表示返回值
12~20
t0~t8
不需保存的暂存器
21
Reserved
暂时保留不用
22
fp
Frame Pointer,栈帧指针
23-31
s0~s8
寄存器变量,子程序使用需要保存和恢复
+
+
+
以上几点都有助于提升编译器生成的代码的性能。曾有实验表明,在完全相同的微结构和外部配置环境下,LoongArch指令系统的SPEC CPU 2006基准程序平均性能比MIPS高15%左右,其中部分性能来自指令集的优化,部分性能来自更高效的ABI。
@@ -569,35 +672,98 @@ struct {short x:10 ; short y:12;}是一个32位类型,x为9-0位,y为27-16
7.若浮点寄存器$fs0-$fs11的值不超过FLEN位宽,那么在函数调用返回时应该保证它们的值和入口时一致。
可以看到,函数调用约定包含许多细节。为了提高效率,LoongArch的调用约定在参考MIPS的基础上做了较多优化。例如,它最多能同时用8个定点和8个浮点寄存器传递16个参数,而MIPS中能用定点或者浮点寄存器来传递的参数最多为8个。
我们来看几个例子。图4.1的程序用gcc -O2 fun.c -S得到汇编文件(见图4.2,略有简化,下同)。可以看到,对于第9个浮点参数,已经没有浮点参数寄存器可用,此时根据浮点调用规范第4条,剩下的参数按整型调用规范传递。因此,a9、a10、a11和a12分别用$a0-$a3这四个定点寄存器来传递,虽然这段代码引用的a9和a11实际上是浮点数。
-
-
-图 4.1: fun.c源代码
+
+
+
+图 4.1: fun.c源代码
+
-
-
-图 4.2: fun.c对应的LoongArch汇编代码
+
+
+
+图 4.2: fun.c对应的LoongArch汇编代码
+
这个程序在MIPS N64 ABI下的参数传递方式则有所不同。按MIPS ABI规则,前八个参数仍然会使用浮点参数寄存器传递,但是后四个参数将通过栈上的内存空间传递,因此a9和a11会从栈中获取,如图4.3所示。
-
-
-图 4.3: fun.c对应的MIPS汇编代码
+
+
+
+图 4.3: fun.c对应的MIPS汇编代码
+
对于可变数量参数的情况,图4.4给出了一个测试案例,表4.3是对应的参数传递表。可以看到,第一个固定参数是浮点参数,用$fa0,后续的可变参数根据浮点调用规范第2条全部按整型调用规范传递,因此不管是浮点还是定点参数,都使用定点寄存器。
-
-
-图 4.4: varg.c源代码
+
+
+
+图 4.4: varg.c源代码
+
-
-
-表 4.3: varg.c对应的LoongArch参数传递
+
+
+表 4.3: varg.c对应的LoongArch参数传递
-
+参数序号
传递方式
低位 高位
0
$fa0
(扩展为double)1
1
$a0
(扩展为double)2
2
$a1
第1和2字节为3和4,其余为填充
3
$a2
(long double低64位)5
4
$a3
(long double高64位)5
5
$a4
(扩展为double)6
6
$a5
(扩展到64位)7
7
$a6
8
8
$a7
(扩展为double)9
9
内存$sp + 0
10
+
+
+
4.1.3 进程虚拟地址空间
-虚拟存储管理为每个进程提供了一个独立的虚拟地址空间,指令系统、操作系统、工具链和应用程序会互相配合对其进行管理。首先,指令系统和OS会决定哪些地址空间用户可以访问,哪些只能操作系统访问,哪些是连操作系统也不能访问的保留空间。然后工具链和应用程序根据不同的需要将用户可访问的地址空间分成几种不同的区域来管理。图4.5展示了一个典型C程序运行时的用户态虚拟内存布局。
-
-可以看到,C程序的典型虚拟内存布局包括如下几部分:
+虚拟存储管理为每个进程提供了一个独立的虚拟地址空间,指令系统、操作系统、工具链和应用程序会互相配合对其进行管理。首先,指令系统和OS会决定哪些地址空间用户可以访问,哪些只能操作系统访问,哪些是连操作系统也不能访问的保留空间。然后工具链和应用程序根据不同的需要将用户可访问的地址空间分成几种不同的区域来管理。图4.5展示了一个典型C程序运行时的用户态虚拟内存布局。
+
+
+
+图 4.5: C程序的典型虚拟内存布局
+
+
+可以看到,C程序的典型虚拟内存布局包括如下几部分:
- 应用程序的代码、初始化数据和未初始化数据
- 堆
@@ -606,9 +772,11 @@ struct {short x:10 ; short y:12;}是一个32位类型,x为9-0位,y为27-16
应用程序的代码来自应用程序的二进制文件。工具链在编译链接应用程序时,会将代码段地址默认设置为一个相对较低的地址(但这个地址一般不会为0,地址0在多数操作系统中都会被设为不可访问的地址,以便捕获空指针访问)。运行程序时操作系统中的装载器根据程序文件记录的内存段信息把代码和数据装入相应的虚拟内存地址。有初始值的全局变量和静态变量存放在文件的数据段中。未初始化的变量只需要在文件中记录其大小,装载器会直接给它分配所需的内存空间,然后清零。未初始化数据段之上是堆空间。堆用于管理程序运行过程中动态分配的内存,C程序中用malloc分配的内存由堆来管理。接近用户最高可访问地址的一段空间被用作进程的栈。栈向下增长,用先进后出的方式分配和释放。栈用作函数的临时工作空间,存储C程序的局部变量、子函数参数和返回地址等函数执行完就可以抛弃的数据(栈的详细管理情况参见下节)。堆需要支持任意时刻分配和释放不同大小的内存块,需要比较复杂的算法支持,因此相应的分配和释放开销也比较大。而栈的分配和释放实质上只是调整一个通用寄存器$sp,开销很小,但它只能按先进后出的分配次序操作。应用程序用到的动态函数库则由动态链接程序在空闲空间中寻找合适的地址装入,通常是介于栈和堆之间。
图4.6是64位Linux系统中一个简单C程序(程序名为hello)运行时的虚拟内存布局的具体案例。它基本符合上述典型情况。栈之上的三段额外空间是现代Linux系统的一些新特性引入的,有兴趣的读者可以自行探究。
-
-
-图 4.6: 一个简单C程序的虚拟内存布局
+
+
+
+图 4.6: 一个简单C程序的虚拟内存布局
+
需要说明的是,一般来说ABI并不包括进程地址空间的具体使用约定。事实上,进程虚拟内存布局一般也不影响应用程序的功能。我们可以通过一些链接器参数来改变程序代码段的默认装载地址,让它出现在更高的地址上;也可以在任意空闲用户地址空间内映射动态链接库或者分配内容。这里介绍一些典型的情况是为了让读者更好地理解软硬件如何协同实现程序的数据管理及其装载和运行。
@@ -616,41 +784,57 @@ struct {short x:10 ; short y:12;}是一个32位类型,x为9-0位,y为27-16
4.1.4 栈帧布局
像C/C++这样的高级语言通常会用栈来管理函数运行过程使用的一些信息,包括返回地址、参数和局部变量等。栈是一个大小可以动态调整的空间,在多数指令系统中是从高地址向下增长。如图4.7所示,栈被组织成一个个栈帧(一段连续的内存地址空间),每个函数都可以有一个自己的栈帧。调用一个子函数时栈增大,产生一个新的栈帧,函数返回时栈减小,释放掉一个栈帧。栈帧的分配和释放在有些ABI中由调用函数负责,在有些ABI中由被调用者负责。
我们以LoongArch LP64为例看看具体的案例。图4.7是最完整的情况,它同时利用了$sp和$fp两个寄存器来维护栈帧。$sp寄存器指向栈顶,$fp寄存器指向当前函数的栈帧开始处。编译器为函数在入口处生成一个函数头(Prologue),在返回处生成一个函数尾(Epilogue),它们负责调整$sp和$fp寄存器以生成新的栈帧或者释放一个栈帧,并生成必要的寄存器保存和恢复代码。
-
-
-图 4.7: 使用帧指针寄存器的栈帧布局
+
+
+
+图 4.7: 使用帧指针寄存器的栈帧布局
+
图4.8的简单函数用gcc -O2 -fno-omit-frame-pointer -S来编译,会产生图4.9这样的汇编代码(为清晰起见,将形如$rxx的寄存器名替换为约定的助记符,下同)。
-
-
-图 4.8: 一个简单的simple函数
+
+
+
+图 4.8: 一个简单的simple函数
+
-
-
-图 4.9: simple函数的汇编代码
+
+
+
+图 4.9: simple函数的汇编代码
+
前3条指令属于函数头,第一条指令设立了一个16字节的栈帧(LP64要求栈帧以16字节对齐),第二条指令在偏移8的位置保存了$fp寄存器,第三条指令则把$fp指向刚进入函数时的$sp。第4条和第7条指令属于函数尾,分别负责恢复$fp和释放栈帧。当然,很容易看到,对这么简单的情况,维护栈帧完全是多余的,因此如果不加-fno-omit-frame-pointer强制使用$fp的话,gcc -O2 -S生成的代码将会如图4.10所示,整个函数不再产生和释放栈帧。
-
-
-图 4.10: simple函数不保留栈帧指针的编译结果
+
+
+
+图 4.10: simple函数不保留栈帧指针的编译结果
+
大部分函数可以只用$sp来管理栈帧。如果在编译时能够确定函数的栈帧大小,编译器可以在函数头分配所需的栈空间(通过调整$sp),这样在函数栈帧里的内容都有一个编译时确定的相对于$sp的偏移,也就不需要帧指针$fp了。例如图4.11中的normal函数,用gcc -O2 -S编译的结果如图4.12所示。normal函数调用了一个有9个整数参数的外部函数,这样它必须有栈帧来为调用的子函数准备参数。可以看到,编译器生成了一个32字节的栈帧,把最后一个浮点参数9保存到偏移0,把返回地址$ra保存到偏移24。
-
-
-图 4.11: normal函数代码
+
+
+
+图 4.11: normal函数代码
+
-
-
-图 4.12: normal函数的gcc -O2编译结果
+
+
+
+图 4.12: normal函数的gcc -O2编译结果
+
但有时候可能无法在编译时确定一个函数的栈帧大小。在某些语言中,可以在运行时动态分配栈空间,如C程序的alloca调用,这会改变$sp的值。这时函数头会使用$fp寄存器,将其设置为函数入口时的$sp值,函数的局部变量等栈帧上的值则用相对于$fp的常量偏移来表示。图4.13中的函数用alloca动态分配栈空间,导致编译器生成带栈帧指针的代码。如图4.14所示,$fp指向函数入口时$sp的值,$sp则先减32字节留出调用子函数的参数空间以及保存$fp和$ra的空间,然后再为alloca(64)减去64以动态分配栈空间。
-
-
-图 4.13: dynamic函数源代码
+
+
+
+图 4.13: dynamic函数源代码
+
-
-
-图 4.14: dynamic函数的汇编代码
+
+
+
+图 4.14: dynamic函数的汇编代码
+
@@ -666,9 +850,11 @@ struct {short x:10 ; short y:12;}是一个32位类型,x为9-0位,y为27-16
4.2.2 异常和中断
上一章已经介绍了异常和中断的概念及其常规处理流程。通常异常和中断的处理对用户程序来说是透明的,相关软硬件需要保证处理前后原来执行中的代码看到的CPU状态保持一致。这意味着开始异常和中断处理程序之前需要保存所有可能被破坏的、原上下文可见的CPU状态,并在处理完返回原执行流之前恢复。需要保存的上下文包括异常处理代码的执行可能改变的寄存器(如Linux内核自身不用浮点部件,因此只需要处理通用整数寄存器而无须处理浮点寄存器)、发生异常的地址、处理器状态寄存器、中断屏蔽位等现场信息以及特定异常的相关信息(如触发存储访问异常的地址)。异常和中断的处理代码通常在内核态执行,如果它们触发前处理器处于用户态,硬件会自动切换到内核态。这种情况下通常栈指针也会被重新设置为指向内核态代码所使用的栈,以便隔离不同特权等级代码的运行信息。
对于非特别高频的异常或者中断,操作系统往往会统一简化处理,直接保存所有可能被内核修改的上下文状态,然后调用相应的处理函数,最后再恢复所有状态。因为大部分情况下处理函数的逻辑比较复杂,所以算起开销比例来这么做的代价也可以接受。例如,3A5000处理器的Linux内核中,所有中断都采用统一的入口处理代码,它的主要工作就是保存所有的通用整数寄存器和异常现场信息,除此之外只有少量指令用于切换中断栈、调用实际中断处理函数等代码。入口处理的指令总共只有几十条,而一个有实际用处的中断处理过程一般至少有数百条指令,其中还包括一些延迟比较长的IO访问。例如,看上去很简单的键盘中断处理,在把输入作为一个事件报告到Linux内核的输入子系统之前,就已经走过了如图4.15所示那么多的函数。
-
-
-图 4.15: 键盘输入的中断处理部分路径
+
+
+
+图 4.15: 键盘输入的中断处理部分路径
+
except_vec_vi是Linux/LoongArch内核的向量中断入口处理代码,之后它会用USB键盘对应的中断号为参数调用do_IRQ函数,do_IRQ再经过一系列中断框架处理后调用usb的中断处理函数usb_hcd_irq,读入相应的键码,最后用input_event报告给输入子系统,输入子系统再负责把输入事件传递给适当的应用程序。感兴趣的读者可以阅读Linux内核相关代码以更深入地理解这个过程,在此不再展开。
对于发生频率很高的异常或者中断,我们希望它的处理效率尽量高。从异常和中断处理的各个环节都可以设法降低开销。例如,可以通过专用入口或者向量中断技术来降低确定异常来源和切换指令流的开销。此外,不同的指令系统用不同的方法来降低上下文保存恢复的开销。例如TLB管理,上一章中我们介绍了LoongArch中TLB重填的做法:设置专门的异常入口,利用便签寄存器来快速获得可用的通用寄存器,以及提供两个专门的指令(lddir和ldpte)来进一步加速从内存页表装入TLB表项的过程。X86指令系统选择完全用硬件来处理,成功的情况不会发出异常。MIPS指令系统则采用预留两个通用寄存器的办法。TLB重填异常处理只用这两个寄存器,因此没有额外的保存恢复代价(但所有的应用程序都牺牲了两个宝贵的通用寄存器)。
@@ -676,11 +862,61 @@ struct {short x:10 ; short y:12;}是一个32位类型,x为9-0位,y为27-16
4.2.3 系统调用
系统调用是操作系统内核为用户态程序实现的子程序。系统调用的上下文切换场景和函数调用比较类似,和普通调用相比主要多了特权等级的切换。Linux操作系统中的部分系统调用如表4.4所示。一些系统调用(如gettimeofday系统调用)只返回一些内核知道但用户程序不知道的信息。系统调用要满足安全性和兼容性两方面的要求。安全性方面,在面对错误甚至恶意的应用时,内核应该是健壮的,应能保证自身的安全;兼容性方面,操作系统内核应该能够运行已有的应用程序,这也要求系统调用应该是兼容的,轻易移除一个系统调用是无法接受的。
-
-
-表 4.4: Linux/LoongArch操作系统的部分系统调用
+
+
+表 4.4: Linux/LoongArch操作系统部分系统调用
-
+类型
系统调用
调用号
作用
进程控制
clone
220
克隆一个进程
execv
221
执行一个程序
文件读写
read
63
读文件
write
64
写文件
文件系统
mkdir
34
创建目录
mount
40
挂载文件系统
系统控制
gettimeofday
169
获取系统时间
reboot
142
重新启动
内存管理
mmap
222
映射虚拟内存页
信号量
semctl
191
信号量控制
+
+
+
Linux内核中,每个系统调用都被分配了一个整数编号,称为调用号。调用号的定义与具体指令系统相关,X86和MIPS对同一函数的调用号可能不同。Linux/LoongArch系统的调用号定义可以从内核源码include/uapi/asm-generic/unistd.h获得。
因为涉及特权等级的切换,系统调用通常被当作一种用户发起的特殊异常来处理。例如在LoongArch指令系统中,执行SYSCALL指令会触发系统调用异常。异常处理程序通过调用号查表找到内核中相应的实现函数。与所有异常一样,系统调用在返回时使用ERTN指令来同时完成跳转用户地址和返回用户态的操作。
类似于一般的函数调用,系统调用也需要进行参数的传递。应该尽可能使用寄存器进行传递,这可以避免在核心态空间和用户态空间之间进行不必要的内容复制。在LoongArch指令系统中,系统调用的参数传递有以下约定:
@@ -690,9 +926,11 @@ struct {short x:10 ; short y:12;}是一个32位类型,x为9-0位,y为27-16
4)系统调用保存$s0-$s8寄存器的内容,不保证保持参数寄存器和暂存寄存器的内容。
为了保障安全性,内核必须对用户程序传入的数组索引、指针和缓冲区长度等可能带来安全风险的参数进行检查。从用户空间复制数据时,应用程序提供的指针可能是无效的,直接在内核使用可能导致内核崩溃。因此,Linux内核使用专用函数copy_to_user()和copy_from_user()来完成与用户空间相关的复制操作。它们为相应的访存操作提供了专门的异常处理代码,避免内核因为用户传入的非法值而发生崩溃。
图4.16展示了一个汇编语言编写的write系统调用的例子。用gcc编译运行,它会在屏幕上输出“Hello World!”字符串。当然,通常情况下应用程序不用这样使用系统调用,系统函数库会提供包装好的系统调用函数以及更高层的功能接口。比如,glibc库函数write包装了write系统调用,C程序直接用write(1,“Hello World!\n”,14)或者用更高层的功能函数printf(“Hello World\n”)就可以实现同样的功能。
-
-
-图 4.16: 调用write系统调用输出字符串
+
+
+
+图 4.16: 调用write系统调用输出字符串
+
@@ -718,11 +956,61 @@ struct {short x:10 ; short y:12;}是一个32位类型,x为9-0位,y为27-16
4.2.7 六种上下文切换场景的对比
表4.5对以上六种上下文切换的场景进行了对比总结。函数调用和系统调用是用户主动发起的,因此可以通过ABI约定来避免不必要的保存恢复。其他几种场景通常都要达到对应用程序透明的效果,因此切换后可能被修改的状态都应该被保存和恢复。
-
-
-表 4.5: 六种上下文切换场景
+
+
+表 4.5: 六种上下文切换场景
-
+场景
上下文切换时保存和恢复的内容
函数调用
部分寄存器(包括栈帧相关的$sp,$fp)、返回地址
中断和异常
(通常情况)全部定点寄存器、异常现场信息、异常相关信息
系统调用
部分定点寄存器(包括栈帧相关寄存器)、异常现场信息
线程
全部用户态寄存器、TLS、当前PC等相关信息
进程
全部用户态寄存器、页表基址等控制寄存器、当前PC等相关信息
虚拟机
虚拟CPU状态(寄存器、必要的特权资源等)
+
+
+
diff --git a/运算器设计.html b/运算器设计.html
index 6975fda..4667bf3 100644
--- a/运算器设计.html
+++ b/运算器设计.html
@@ -216,8 +216,9 @@ div.csl-indent {
- 2.5 RISC指令集比较
- 2.6 C语言的机器表示
@@ -290,6 +291,7 @@ div.csl-indent {
- 5.3.2 CPU-北桥-南桥三片结构
- 5.3.3 CPU-弱北桥-南桥三片结构
- 5.3.4 CPU-南桥两片结构
+- 5.3.5 SoC单片结构
- 5.4 处理器和IO设备间的通信
@@ -423,16 +425,17 @@ div.csl-indent {
- 11.2.3 Cache一致性协议
- 11.3 多核处理器的互连结构
-- 11.4 典型多核处理器
+
- 11.4 多核处理器的同步机制
+- 11.5 典型多核处理器
-- 11.5 本章小结
-- 11.6 习题
+- 11.6 本章小结
+- 11.7 习题
- VI 系统性能评价与分析
- 12 计算机系统性能评价与性能分析
@@ -511,6 +514,58 @@ div.csl-indent {
上面的定义只回答了非负数或无符号整数的二进制表示问题。有关正负整数的表示问题会在8.1.1节讨论。下面举例说明无符号二进制整数的表示和加法。
例用4位二进制编码,计算5+9。
解5的4位二进制表示为\(0101_{2}\),9的4位二进制表示为\(1001_{2}\)。5+9列竖式计算如下:
+
+0101
+1001
1110
+
+
+
上面的竖式计算过程和人们日常的十进制竖式加法计算过程极为相似。所不同的仅在于,十进制是“逢十进一”,二进制是“逢二进一”。
计算机内部的所有数据都采用二进制编码表示,但是在表示绝对值较大的数据时需要很多位,不利于书写和阅读,因此经常采用十六进制编码来记录数据。因为16恰为\(2^4\),所以二进制和十六进制相互转换时不会出现除不尽的情况,可以非常快捷地进行两种进制的转换运算。具体做法是,将一个数由二进制编码转换为十六进制编码时,从小数点开始,向左、向右两个方向,每4个二进制位一组(不足时小数点左侧的左补0,小数点右侧的右补0),直接替换为一个十六进制位。十六进制编码转换为二进制编码的方法类似,只是每个十六进制位替换为4个二进制位。
@@ -550,17 +605,71 @@ div.csl-indent {
计算机中的浮点数表示沿用了科学记数法的表示方式,即包含了符号、尾数和阶码三个域。符号用一位二进制码表示,0为正,1为负。然而在计算机内部位宽是有限的,余下的尾数和阶码两者间存在一个此消彼长的关系,需要设计者在两者间权衡:增加尾数的位宽会提高表示的精度但是会减少表示的范围,而增加阶码的位宽虽然扩大了表示的范围但是会降低表示的精度。因为浮点数规格的定义融入了设计者自身的考虑,所以直到20世纪80年代初,浮点数表示格式还没有统一标准,不同厂商的计算机内部的浮点数表示格式存在差异。这导致在不同厂商计算机之间进行含有浮点数的数据传送或程序移植时,必须进行数据格式的转换,更为糟糕的是,有时这种数据格式转换会带来运算结果不一致的问题。因此,从20世纪70年代后期开始,IEEE成立委员会着手制定统一的浮点数标准,最终在1985年完成了浮点数标准IEEE 754的制定。该标准的主要起草者是美国加州大学伯克利分校数学系教授William Kahan,他帮助Intel公司设计了8087浮点协处理器,并以此为基础形成了IEEE 754标准,他本人也因此获得了1987年的图灵奖。自IEEE 754标准颁布后,目前几乎所有的计算机都遵循该标准来表示浮点数。在过去的几十年间,IEEE 754标准也根据工业界在CPU研发过程中遇到的新需求、实现的新结构,及时进行演进和完善。其中一个比较重要的版本是2008年更新的IEEE 754-2008。该版本中明确了有关融合乘加(Fused Multiply-Add)运算、半精度浮点数等方面的内容。本书仅介绍IEEE 754标准中涉及单精度、双精度浮点数表示的基本内容,对其他内容感兴趣的读者可查阅相关文献。
(3)IEEE 754标准浮点数格式
IEEE 754标准中定义了两种基本的浮点数格式:32位的单精度格式和64位的双精度格式,如图8.1所示。
-
-
-图 8.1: IEEE 754浮点数格式
+
+
+
+图 8.1: IEEE 754浮点数格式
+
32位单精度格式中包含1位符号、8位阶码和23位尾数;64位双精度格式中包含1位符号、11位阶码和52位尾数。两种格式下基数均隐含为2。
IEEE 754标准中,尾数用原码表示。由于表示同一个数的时候尾数可以有多种表示,例如\(0.001_{2}\)可以表示为\(0.1_{2}\times 2^{-2}\),也可以表示成\(1.0_{2}\times 2^{-3}\),因此需要一个规格化的表示来使得表示唯一。IEEE 754标准中规格化尾数的表示统一为1.xxxx的形式。尾数规格化后第一位总为1,因而可以在尾数中缺省这一位1。隐藏该位后尾数可以多一位表示,精度提高一位。
IEEE 754标准中,阶码是用减偏置常量的移码表示,但是所用的偏置常量并不是通常n位移码所用的\(2^{n-1}\),而是\((2^{n-1}-1)\),因此,单精度和双精度浮点数的偏置常量分别为127和1023。
IEEE 754标准对浮点数的一些情况做了特殊的规定,总的来说可以分为5种情况,主要用阶码进行区分,表8.1给出了IEEE 754标准中单精度和双精度不同浮点数的表示。
-
-表 8.1: IEEE 754 浮点数格式
+
+
+表 8.1: IEEE 754 浮点数格式
+
单精度
双精度
符号
阶码
尾数
值
符号
阶码
尾数
值
正无穷
0
255
0
∞
0
2047
0
∞
负无穷
1
255
0
-∞
1
2047
0
-∞
非数(NaN)
0或1
255
≠0
NaN
0或1
2047
≠0
NaN
规格化非0正数
0
0<e<255
f
1.f×2e-127
0
0<e<2047
f
1.f×2e-1023
规格化非0负数
1
0<e<255
f
-1.f×2e-127
1
0<e<2047
f
-1.f×2e-1023
非规格化非0正数
0
0
f≠0
0.f×2e-126
0
0
f≠0
0.f×2e-1022
非规格化非0负数
1
0
f≠0
-0.f×2e-126
1
0
f≠0
-0.f×2e-1022
正0
0
0
0
0
0
0
0
0
负0
1
0
0
0
1
0
0
0
+
+
+
(1)无穷大:阶码全1尾数全0
引入无穷大是为了在出现浮点计算异常时保证程序能够继续执行下去,同时也为程序提供一种检测错误的途径。\(+\infty\)在数值上大于所有有限浮点数,\(-\infty\)在数值上小于所有有限浮点数。无穷大不仅可以是运算的结果,也可以作为运算的源操作数。当无穷大作为源操作数时,根据IEEE 754标准规定,可以得到无穷大或非数的结果。
(2)非数(NaN):阶码全1尾数非0
@@ -579,27 +688,35 @@ div.csl-indent {
- 半导体
MOS晶体管使用硅作为基本材料。在元素周期表中,硅是IV族元素,它的原子最外层有4个电子,可以与相邻的4个硅原子的最外层电子配对形成共价键。图8.2a给出了纯净硅中原子连接关系的一个简单二维平面示意,实际上纯净硅中原子构成的是一个正四面体立体网格。通过与相邻原子形成的共价键,纯净硅中所有原子的最外层都具有8个电子,达到相对稳定,所以纯净硅的导电性很弱。但是,如果在纯净硅中掺杂少量5价的原子(如磷),这些原子将挤占原有硅原子的位置,而由于这些原子的最外层有5个电子,除了与原有硅原子形成共价键用掉4个电子外,还多余一个处于游离状态的电子,如图8.2b所示。在电场的作用下,处于游离状态的电子就会逆着电场方向流动,形成负电流。这类材料被称为N(Negative)型材料。同样,如果在纯净的硅中掺杂少量3价的原子(如硼),那么这些原子挤占原有硅原子的位置后,其最外层还缺少一个电子和相邻的硅原子形成共价键,形成空穴,如图8.2c所示。在电场的作用下,周围的电子就会跑过来填补这个空穴,从而留下一个新的空穴,相当于空穴也在顺着电场方向流动,形成正电流。这类材料被称为P(Positive)型材料。当非4价元素掺杂的含量较小时,产生的电子和空穴也就比较少,用—号表示;当非4价元素掺杂的含量较大时,产生的电子和空穴也就比较多,用+号表示。因此,P-表示掺杂浓度低的P型材料,里面只有少量的空穴;N+表示掺杂浓度高的N型材料,里面有大量电子。
-
-
-图 8.2: 半导体硅原子结构示意图
+
+
+
+图 8.2: 半导体硅原子结构示意图
+
- NMOS和PMOS晶体管
如图8.3所示,MOS晶体管是由多层摞放在一起的导电和绝缘材料构建起来的。每个晶体管的底部叫作衬底,是低浓度掺杂的半导体硅。晶体管的上部接出来3个信号端口,分别称为源极(Source)、漏极(Drain)和栅极(Gate)。源极和漏极叫作有源区,该区域内采用与衬底相反极性的高浓度掺杂。衬底是低浓度P型掺杂,有源区是高浓度N型掺杂的MOS晶体管叫作NMOS晶体管;衬底是低浓度N型掺杂,有源区是高浓度P型掺杂的MOS晶体管叫作PMOS晶体管。无论是NMOS管还是PMOS管,其栅极与衬底之间都存在一层绝缘体,叫作栅氧层,其成分通常是二氧化硅(SiO2)。最早期的MOS晶体管栅极由金属制成,后来的栅极采用掺杂后的多晶硅制成。掺杂后的多晶硅尽管其电阻比金属大,但却比半导体硅的电阻小很多,可以作为电极。并且同普通金属相比,多晶硅更耐受高温,不至于在MOS晶体管生产过程中融化。不过最新的工艺又有重新采用金属栅极的。
-
-
-图 8.3: MOS晶体管组成结构示意图
+
+
+
+图 8.3: MOS晶体管组成结构示意图
+
上面简述了MOS晶体管的基本构成,下面以NMOS晶体管为例介绍MOS晶体管的工作原理。如果单纯在源极、漏极之间加上电压,两极之间是不会有电流流过的,因为源极和漏极之间相当于有一对正反相对的PN结,如图8.4a所示。如果先在栅极上加上电压,因为栅氧层是绝缘的,就会在P衬底里形成一个电场。栅极上的正电压会把P衬底里面的电子吸引到栅氧层的底部,形成一个很薄的沟道电子层,相当于在源极和漏极之间架起了一座导电的桥梁。此时如果再在源极、漏极之间加上电压,那么两极之间的电流就能流过来了,如图@ref(fig:NMOS_work)b所示。NMOS的基本工作原理就是这样,但是其实际的物理现象却很复杂。
-
-
-图 8.4: NMOS晶体管工作原理示意图
+
+
+
+图 8.4: NMOS晶体管工作原理示意图
+
当我们屏蔽掉底层的物理现象细节,对MOS晶体管的工作行为进行适度抽象后,NMOS晶体管的工作行为就是:在栅极上加上电就通,不加电就断。PMOS晶体管的工作行为与NMOS晶体管的恰好相反,加上电就断,不加电就通。这样我们可以简单地把MOS晶体管当作开关。NMOS晶体管是栅极电压高时打开,栅极电压低时关闭;PMOS晶体管反过来,栅极电压低时打开,栅极电压高时关闭。如图8.5所示。随着工艺的发展,MOS晶体管中栅氧层的厚度越来越薄,使得开启所需的栅极电压不断降低。晶体管的工作电压从早期工艺的5.0V,降到后来的2.5V、1.8V,现在都是1V左右或更低。
-
-
-图 8.5: MOS晶体管开关行为
+
+
+
+图 8.5: MOS晶体管开关行为
+
尽管MOS晶体管可以表现出开关的行为,但是单纯的PMOS晶体管或者NMOS晶体管都不是理想的开关。例如,NMOS晶体管适合传输0而不适合传输1;PMOS晶体管恰好相反,适合传输1而不适合传输0。在后面讲述常见CMOS电路时,将会论及如何解决这一问题。
@@ -611,6 +728,58 @@ div.csl-indent {
(1)布尔代数
数字逻辑基于的数学运算体系是布尔代数。布尔代数是在二元集合{0, 1}基础上定义的。最基本的逻辑运算有三种:与(AND,&)、或(OR,|)、非(NOT,~)。这三种逻辑关系定义如下:
+
+A
B
A & B
A | B
~A
0
0
0
0
1
0
1
0
1
1
1
0
0
1
0
1
1
1
1
0
+
+
+
常用的布尔代数运算定律有:
恒等律:\(A \ |\ 0 = A,\ A \ \&\ 1 = A\);
0/1律:\(A \ |\ 1 = 1,\ A \ \&\ 0 = 0\);
@@ -623,83 +792,209 @@ div.csl-indent {
根据电路是否具有数据存储功能,可将数字逻辑电路分为组合逻辑电路和时序逻辑电路两类。
(2)组合逻辑
组合逻辑电路中没有数据存储单元,电路的输出完全由当前的输入决定。在组合逻辑的各种表达方式中,最简单的就是真值表,即对每一种可能的输入组合给出输出值。显然一个N输入的电路就有\(2^{N}\)种不同的输入组合。常见的门级组合逻辑除了与门(AND)、或门(OR)、非门(NOT),还有与非门(NAND)、或非门(NOR)、异或门(XOR)。图8.6给出了这些常见的门逻辑符号及其真值表。
-
-
-图 8.6: 常用基本逻辑门电路
+
+
+
+图 8.6: 常用基本逻辑门电路
+
利用基本逻辑门电路可以构成具有特定功能的更大规模的组合逻辑部件,如译码器、编码器、多路选择器、加法器等。加法器和乘法器两类运算逻辑电路我们将在后续章节中介绍。表8.2所示是3-8译码器真值表,把3位信号译码成8位输出,当输入000时,8个输出里面最低位为1,输入为001时,次低位为1,依次类推。表8.3所示是一个8选1选择器的真值表,当CBA为000的时候选择输出第0路\(D_{0}\),为001的时候选择输出第1路\(D_{1}\),依次类推。可以看出选择器可以用译码器加上与门来实现。
-
-表 8.2: 3-8译码器真值表
+
+
+表 8.2: 3-8译码器真值表
-
-表 8.3: 8选1选择器真值表
+ 输入
输出
C
B
A
Y0
Y1
Y2
Y3
Y4
Y5
Y6
Y7
0
0
0
1
0
0
0
0
0
0
0
0
0
1
0
1
0
0
0
0
0
0
0
1
0
0
0
1
0
0
0
0
0
0
1
1
0
0
0
1
0
0
0
0
1
0
0
0
0
0
0
1
0
0
0
1
0
1
0
0
0
0
0
1
0
0
1
1
0
0
0
0
0
0
0
1
0
1
1
1
0
0
0
0
0
0
0
1
+
+
+
+
+
+表 8.3: 8选1选择器真值表
+输入
输出
C
B
A
Y
0
0
0
D0
0
0
1
D1
0
1
0
D2
0
1
1
D3
1
0
0
D4
1
0
1
D5
1
1
0
D6
1
1
1
D7
+
+
+
(3)时序逻辑
时序逻辑电路包含时钟信号和数据存储单元两个要素。时序逻辑电路的特点在于,其输出不但与当前输入的逻辑有关,而且与在此之前曾经输入过的逻辑值有关。
时钟信号是时序逻辑电路的基础,它用于确定时序逻辑元件中的状态在何时发生变化。如图8.7所示,时钟信号是具有固定周期的标准脉冲信号。每个时钟周期分为高、低电平两部分,其中低电平向高电平变化的边沿称为上升沿,高电平向低电平变化的边沿称为下降沿。在CPU设计中,通常使用边沿触发方式来确定时序逻辑状态变化的时间点。所谓边沿触发就是将时钟信号的上升沿或下降沿作为采样的同步点,在每个采样同步点,对时序逻辑电路的状态进行采样,并存储到数据存储单元中。
-
-
-图 8.7: 时钟信号
+
+
+
+图 8.7: 时钟信号
+
数据存储单元是时序逻辑电路中的核心。数据存储单元多由锁存器构成。首先介绍RS锁存器。图8.8是RS锁存器的逻辑图和真值表。RS锁存器包含置位端S(Set)和复位端R(Reset)两个输入端口,R为0、S为1时置输出为1,R为1、S为0时输出为0。在图8.8中,下面与非门的输出接到上面与非门的一个输入,同样上面与非门的输出接到下面与非门的一个输入,通过两个成蝶形连接的与非门构成RS锁存器。RS锁存器与组合逻辑的不同在于,当(R, S)的值从(0, 1)或(1, 0)变成(1, 1)时能够保持输出值的状态不变,从而实现数据的存储。组合的输出只跟输入相关;但是RS锁存器的输入变了,它的输出还能保持原来的值。
-
-
-图 8.8: RS锁存器
+
+
+
+图 8.8: RS锁存器
+
在RS锁存器前面连接上两个与非门,再用时钟C(Clock)控制D输入就构成了如图8.9a所示的电路。当C=0时,R和S都为1,RS锁存器处于保持状态,也就是说当时钟处于低电平时,无论输入D怎样变化,输出都保持原来的值。当C=1时,输出Q与输入D值相同,相当于直通。这就是D锁存器(D Latch)的原理,通过时钟C的电平进行控制,高电平输入,低电平保持。
两个D锁存器以图8.9b所示的方式串接起来就构成了一个D触发器(D Flip-Flop)。当C=0时,第一个D锁存器直通,第二个D锁存器保持;当C=1时,第一个D锁存器保持,第二个D锁存器直通;C从0变为1时,D的值被锁存起来。这就是D触发器的基本原理,它是通过时钟的边沿进行数据的锁存。
-
-
-图 8.9: D锁存器和D触发器
+
+
+
+图 8.9: D锁存器和D触发器
+
实际情况下,由于器件中电流的速度是有限的,并且电容充放电需要时间,所以电路存在延迟。为了保证D触发器正常工作,需要满足一定的延迟要求。例如为了把D的值通过时钟边沿锁存起来,要求在时钟变化之前的某一段时间内D的值不能改变,这个时间叫作建立时间(Setup Time)。另外,在时钟跳变后的一段时间内,D的值也不能变,这个时间就是保持时间(Hold Time)。建立时间和保持时间可以是负数。此外D触发器还有一个重要的时间参数叫作“Clock-to-Q”时间,也就是时钟边沿到来后Q端数据更新为新值的时间。D触发器整个的访问延迟是建立时间加上“Clock-to-Q”时间。图8.10给出了上升沿触发的D触发器的建立时间、保持时间以及“Clock-to-Q”时间的示意。
-
-
-图 8.10: D触发器建立时间、保持时间和Clock-to-Q时间
+
+
+
+图 8.10: D触发器建立时间、保持时间和Clock-to-Q时间
+
- 常见CMOS电路
本节通过若干具体示例,讲述如何用MOS晶体管实现逻辑电路,且所列举的电路都是CMOS电路。关于CMOS电路的基本特点,将在“非门”示例之后予以说明。
(1)非门
-
-
-图 8.11: CMOS电路:非门
+
+
+
+图 8.11: CMOS电路:非门
+
图8.11a是非门(也称作反相器)的CMOS电路,它由一个PMOS晶体管和一个NMOS晶体管组成,其中PMOS晶体管(以下简称“P管”)的源极接电源,NMOS晶体管(以下简称“N管”)的源极接地,两管的漏极连在一起作为输出,栅极连在一起作为输入。如果输入为0(接地),则P管导通,N管关闭,P管的电阻为0(实际电路中P管其实有一定的电阻,大约在几千欧姆),N管的电阻无穷大,输出端的电压就是电源电压\(V_{dd}\),如图8.11b所示。反之,当输入为1的时候,N管导通,P管关闭,N管的电阻为0(实际电路中N管其实有一定的电阻,大约在几千欧姆),P管的电阻无穷大,输出端与电源断开,与地导通,输出端电压为0,如图8.11c所示。这就是反相器CMOS电路的工作原理。
从反相器的工作原理可以看出CMOS电路的基本特征,其关键就在“C”(Complementary,互补)上,即由上下两个互补的部分组成电路,上半部分由P管构成,下半部分由N管构成。上半部分打开的时候下半部分一定关上,下半部分打开的时候上半部分一定关闭。这种电路设计的好处是:在稳定状态,电路中总有一端是关死的,几乎没有直流电流,可以大幅度降低功耗。
(2)与非门
图8.12所示的是一个两输入与非门的CMOS电路,电路上面两个P管并联,下面两个N管串联。两个P管并联后,一头接电源,另一头与两个串联的N管连接。两个N管串联后,一头与并联的P管连接,另一头接地。与非门的两个输入A和B分别连接到一个N管和一个P管,输出端是Y。当A和B中有一个为0,则上面的P管网络导通,下面的N管网络断开,输出端被连接到电源上,即输出Y为1。
-
-
-图 8.12: CMOS电路:与非门
+
+
+
+图 8.12: CMOS电路:与非门
+
(3)或非门
-
-
-图 8.13: CMOS电路:或非门
+
+
+
+图 8.13: CMOS电路:或非门
+
图8.13所示的是一个两输入或非门的CMOS电路,电路上面两个P管串联,下面两个N管并联。两个P管串联后,一头接电源,另一头与两个并联的N管连接。两个N管并联后,一头与串联的P管连接,另一头接地。或非门的两个输入A和B分别连接到一个N管和一个P管,输出端是Y。当A和B中有一个为1,则上面的P管网络断开,下面的N管网络导通,输出端与电源断开,连通到地上,即输出Y为0。
(4)传输门
-
-
-图 8.14: CMOS电路:传输门
+
+
+
+图 8.14: CMOS电路:传输门
+
前面提到过单纯的PMOS晶体管或是NMOS晶体管都不是理想的开关,但是在设计电路时有时需要一个接近理想状态的开关,该开关无论对于0还是1都可以传递得很好。解决的方式也很直观,如图8.14所示,一个P管和一个N管彼此源极连在一起,漏极连在一起,两者的栅极上接上一对极性相反的使能信号。当\(EN\)=0、\(\overline{EN}\)=1时,P管和N管都关闭;当\(EN\)=1、\(\overline{EN}\)=0时,P管和N管都开启。当P管和N管都开启时,无论信号是0还是1,都可以通过最适合传递该信号的MOS晶体管从A端传递到B端。
(5)D触发器
在前面讲述逻辑电路时介绍过如何用逻辑门搭建D触发器,在用CMOS电路实现D触发器时,我们也可以利用CMOS的逻辑门搭建出RS锁存器,进而搭建出D锁存器,并最终得到D触发器。但是考虑到构建D触发器时我们其实真正需要的是开关电路和互锁电路,所以这种构建D触发器的方式消耗的资源过多。图8.15中给出现代计算机中常用的一种D触发器电路结构。该电路的左边(虚线框内部)可以视作一个去除了输出缓冲器的D锁存器,该锁存器存储的值体现在其内部N1点的状态。当\(CLK\)=0,\(\overline{CLK}\)=1时,传输门G1开启、G2关闭,D点的值经由反相器I1和传输门G1传递进来,并通过反相器I2和三态反相器T1反馈至N1点,使该点到达一个稳定状态。当\(CLK\)=1,\(\overline{CLK}\)=0时,传输门G1关闭、G2开启,D点值的变化不再影响到内部N1点,同时N1点的状态经由传输门G2,并通过反相器I3和三态反相器T2反馈至N2点,使N2点处于稳定状态,并将该值传递至输出端Q。
-
-
-图 8.15: CMOS电路:D触发器
+
+
+
+图 8.15: CMOS电路:D触发器
+
- CMOS电路延迟
前面在介绍MOS晶体管原理的时候曾经提到过,真实世界中,PMOS晶体管和NMOS晶体管即便是在导通状态下源极和漏极之间也是有电阻的,栅极和地之间也存在寄生电容。因此CMOS电路工作时输入和输出之间存在延迟,该延迟主要由电路中晶体管的RC参数来决定。
图8.16a是一个CMOS反相器的示意图。其输出端有一个对地电容,主要由本身P管和N管漏极的寄生电容、下一级电路的栅电容以及连线电容组成。反相器输出端从0到1变化时,需要通过P管的电阻对该电容充电;从1到0变化时,该电容的电荷需要通过N管的电阻放电到地端。图8.16b示意了输出电容的充放电过程,其中左图代表充电过程,右图代表放电过程。因此,该反相器输出从0到1变化时的延迟由P管打开时的电阻和输出电容决定;从1到0变化时的延迟由N管打开时的电阻和输出电容决定。图8.16c示意了在该反相器输入端从0变到1、再变回到0的过程中(图中虚线表示),输出端值变化的过程。从中可以看出,反相器从输入到输出的变化是有延迟的,而且反相器的输出不是理想的矩形,而是存在一定的斜率。
-
-
-图 8.16: CMOS反相器的延迟
+
+
+
+图 8.16: CMOS反相器的延迟
+
在芯片设计的时候,需要根据单元的电路结构建立每个单元的延迟模型。一般来说,一个单元的延迟由其本身延迟和负载延迟所构成,而负载延迟又与该单元的负载相关。需要指出的是,用早期工艺生成的晶体管,其负载延迟与负载呈线性关系,但对于深亚微米及纳米工艺,晶体管的负载延迟不再与负载呈线性关系。在工艺厂家给出的单元延迟模型中,通常通过一个二维的表来描述每个单元的延迟,其中一维是输入信号的斜率,另外一维是输出负载。即一个单元的延迟是由输入信号的斜率和输出负载两个值通过查表得到的。
@@ -716,20 +1011,76 @@ div.csl-indent {
\[S=\sim A \ \& \ \sim B \ \& \ C_{in} \ | \ \sim A \ \& \ B \ \& \ \sim C_{in} \ | \ A \ \& \ \sim B \ \& \ \sim C_{in} \ | \ A \ \& \ B \ \& \ C_{in}\]
\[C_{out}=A \ \& \ B \ | \ A \ \& \ C_{in} \ | \ B \ \& \ C_{in}\]
上述表达式中,~表示取反操作,&表示与操作,|表示或操作,其中~操作的优先级最高,&操作次之,|操作优先级最低。上述表达式还可以简单解释为:当输入的三个数中有奇数个1时,本地和为1;当输入的三个数中有两个1时,向高位的进位为1。
-
-表 8.4: 一位全加器真值表
+
+
+表 8.4: 一位全加器真值表
+