Computer Architecture
Computer Architecture
This for test takers to quickly review Computer Architecture. The whole notes contain 5 parts and I try to make it clear and simple. Credit to《Computer Organization and Design : The Hardware/Software Interface, 5th Edition》目前我也在@新领域理工塾,讲授这门课😇
催更|辅导|私塾兼职|联系偷偷:LifeGoesOn_Rio
1. Introduction
在本章中,主要引入一下计算机组成的概念然后讲解衡量计算机的性能指标,并着重讲解涉及到的计量单位和公式。
1.1 常用单位
容量单位常用字节(Byte) ,衡量单位是2进制(Binary) ,容量之间的数量级关系是:$2^{10}$; 常见的有$K$,$M$,$G$,$T$。
例如: $1TB=2^{10} GB= 1024GB$; $1GB=2^{10}MB=1024MB$
衡量传输速度常用位每秒(bps,bit per secod) ,衡量单位是10进制(Decimal) ,传输速度之间的数量级关系是:$10^3$; 常见的有$K$,$M$,$G$,$T$。
例如:$1kbit/s=1000bit/s$, $1mbit/s=1000kbit/s$
另外需要注意的单位换算是:
- $bit/s$记作$bps$
- $1 Byte = 8 bit$
- $1 Bps = 1 Byte/second = 8 bit/second = 8 bps$
1.2 计算机结构中的8个伟大思想
- Moore’s Law: 芯片上的集成度每18~24个月翻一番
- Abstraction:使用抽象来表示不同设计层次,化繁为简
- Common Case Fast: 加速大概率事件远比优化小概率事件更能提高性能,即抓住主要矛盾
- Parallel Performance: 并行操作来提高性能,说白了就是
打工人能做多少做多少 - Pipeline: 这是一个并行场景的具体实现,后面章节会详细讲解🔎
- Prediction:通过猜测的方式提前启动某些操作,提高性能,投资人讲究一个增收降本的策略。
- Hierachy of memory: 用存储器层次来解决容量,速度,成本之间的相互矛盾需求。小而精则贵,大而糙则贱
- Dependable:任何一个物理器件都有可能失效,使用冗余部件的方式来提高系统的可靠性,即备胎🛞
在加速大概率事件中有一个定量分析的Amdahl定律:
1.3 计算机语言
计算机语言主要分为三类:高级语言、汇编语言和机器语言。程序执行的过程是先将高级语言编译为汇编语言,然后通过汇编,将汇编语言转化为相应的机器语言。
1.3.1 高级语言(High-level programming language)
最接近人类自然语言的编程语言。常见的高级语言包括C++, Java, Python, 和JavaScript。
1.3.2 汇编语言(Assembly language)
汇编语言是一种低级编程语言,它直接对应于机器语言,但使用人类更易理解的符号和短语来表示。每条汇编指令通常都对应一条机器语言指令,因此汇编语言被认为是机器语言的符号表示。汇编语言需要使用汇编器(Assembler)将代码转换为机器语言。这种语言通常用于编写需要精确控制硬件的程序,如操作系统内核和驱动程序。汇编语言的分类一般有两种主要类型:RISC指令集(Reduced Instruction Set Computing) 和 CISC指令集(Complex Instruction Set Computing)。
- RISC指令集: 即简化指令集计算(Reduced Instruction Set Computing):这种类型的指令集特点是指令集较为简单,每条指令执行时间固定而短,通常在一个时钟周期内完成。这使得RISC处理器能够更快速地执行大量简单的指令。代表性的RISC架构包括ARM、MIPS和SPARC。
- CISC指令集:即复杂指令集计算(Complex Instruction Set Computing),这种类型的指令集特点是指令集复杂,每条指令可能需要多个时钟周期才能完成执行。CISC处理器通过一个复杂的指令集试图降低编程的复杂性,使得某些复杂操作能通过一条指令完成。典型的CISC架构包括x86和VAX。
在修考中,出现的汇编语言是RISC指令集,因此在后序章节只讲解RISC指令
1.3.3 机器语言(Machine language)
机器语言是计算机能够直接执行的最底层编程语言。它由二进制代码组成,每条指令由一串0和1表示(eg. 00000111111010001),直接控制计算机的硬件操作。由于其复杂性和难以阅读,人类很少直接编写机器语言程序。
总结:高级语言编译为汇编语言,然后汇编语言通过汇编转化为由01串组成的机器语言
1.4 冯·诺伊曼结构(Von Neumann architecture)
输入设备(Input Devices) 是用于向计算机输入数据和指令的硬件。例如,键盘(Keyboard)、鼠标(Mouse)、扫描仪(Scanner)、话筒(Microphone)等。它们将用户的操作转化为计算机能够理解的信号。
输出设备(Output Devices) 是用于从计算机输出数据和结果的硬件。例如,显示器(Monitor)、打印机(Printer)、扬声器(Speakers)等。它们将计算机处理后的数据转化为用户可以理解的信息。
控制器(Control Unit, CU) 是负责从存储器读取指令,并解释和执行指令的计算机部件。它管理和协调计算机的各个部分,以确保指令被按顺序执行。
运算器(Arithmetic Logic Unit, ALU) 是执行算术运算和逻辑运算的核心部件。它能处理整数和浮点数的加、减、乘、除等运算,也能进行与、或、非等逻辑运算。
存储器(Memory) 是存储数据和指令的地方。分为主存储器(Main Memory)和辅助存储器(Secondary Storage)。主存储器(如RAM,随机存取存储器,Random Access Memory)用于存储当前正在使用的数据和指令;辅助存储器(如硬盘,Hard Disk Drive)用于长期存储数据。
中央处理器(Central Processing Unit, CPU) 是计算机的“大脑”,负责解释和执行指令。它由控制器(Control Unit, CU)和运算器(Arithmetic Logic Unit, ALU)组成,管理并处理所有的计算任务。
1.5 计算机性能指标
响应时间(Response Time): 响应时间,又称执行时间,是指系统从接收到请求到开始产生输出结果所经过的时间。
吞吐率(Throughput): 吞吐率是指在单位时间内系统能够处理的请求数量。它衡量的是系统的处理能力。高吞吐率意味着在同样的时间内,系统能够处理更多的请求或完成更多的工作。
CPU性能公式(Performance):
A的性能比B的性能高$X$倍计算: $X=t_{B执行时间}/t_{A执行时间}$
时钟周期长度(Clock Cycle Length):
是指计算机时钟信号中相邻两个相同状态(如两个上升沿)之间的时间间隔。CPU做的任何动作所消耗的时间都是一个时钟周期的整数倍。通常以纳秒(ns)为单位,记作 $T$
时钟周期频率(Clock Cycle Frequency):
则是时钟信号每秒钟振荡的次数,通常以赫兹(Hz)为单位。它是时钟周期长度的倒数。例如,如果时钟周期长度是2纳秒,那么时钟频率就是0.5 GHz,记作 $f$
两者之间的关系是:
时钟周期长度 = 1 / 时钟周期频率, 即 $T = 1 / f$
时钟周期频率 = 1 / 时钟周期长度, 即 $f = 1 / T$
CPI(Cycles Per Instruction):
是计算机性能指标,用于衡量每条指令所需的平均时钟周期数。它反映了CPU执行指令的效率。CPI的计算公式是:
时钟周期数计算公式(Clock Cycle):
注:指令数(Instruction Count, IC)是指CPU在执行一个程序时,需要执行的指令总数。
CPU执行时间:
还有一个衡量指标是MIPS(million instructions per second):
功耗计算公式: 记住就行
注: $P$ 是功耗,$C$ 是负载电容, $U$ 是电压, $f$ 是开关频率
2. Instructions: The Language of Computers
这一章非常重要,因为修考题目中经常出现理解汇编语言的题目。本章主要讲解RISC-V指令集架构还有五种寻址方式
2.1 Instrcucton
计算机在底层执行程序时,通过读取由0和1组成的机器指令来执行命令。在RISC-V指令集中,所有的机器指令都是32位长,也就是32位的二进制串。这些32位的机器指令对应的汇编语言指令通常由操作码和两个地址码组成(形式:操作码 + 地址码 + 地址码)。规定:
- 程序中的变量存放在保存寄存器(store reg)中:$s0~$s7共8个
- 临时变量,中间变量存放在临时寄存器(temp reg)中:$t0~$t7共8个
- 零寄存器,永远存放32位的0,记作$zero
2.2 RISC-V Instruction Set
2.2.1 基本指令
加法,减法指令(add, sub):
加立即数指令(addi):
逻辑运算指令: and(与),or(或),nor(或非)
逻辑左移(shift left logic, sll)和右移(shift right logic, srl):
取字指令(load word, lw)和存字指令(save word, sw):
因为寄存器是按字(Word)存储,而内存是按字节(Byte)编址,在RISC-V指令集中,约定:1 Word = 32 bit = 4 Byte。
举个例子:已知a[0]的地址存放在寄存器s0中,现在需要取a[2]的值存放在寄存器t0中;a[2]和a[0]之间的字节偏移有 2 * 4 = 8 Byte。RISC-V指令可写为:
同理,如果我们要把t0寄存器的值存到a[4]中去,可以知道a[4]与a[0]的字节偏移有 4 * 4 = 16 Byte。RISC-V指令可写为:
2.2.2 装载32位立即数到寄存器
因为装载的32位立即数不能完全放进32位的机器指令中,因此寄存器可以通过两条指令来完成:LUI (Load Upper Immediate) 和 ORI (OR Immediate)。比如将立即数 0xF7EE57AD 装载到寄存器中这个两个指令需要完成的动作是:
0xF7EE57AD 对应的 32 位二进制数是:1111 0111 1110 1110 0101 0111 1010 1101
LUI 指令:取0xF7EE57AD的高16位数(1111 0111 1110 1110) 放入寄存器的高16位
ORI 指令:将寄存器和剩下的低16位数做立即与的动作(0101 0111 1010 1101)
当然还有其他写法:比如lui和addi。例如,要将立即数 0x12345678 装载到寄存器 t0 中
2.2.3 判断和决策指令
BEQ(Branch if Equal) 指令:
- 功能:比较两个寄存器的值,如果它们相等,则跳转到指定的目标地址。
- 格式:
beq rs1, rs2, offset
rs1
:第一个源寄存器rs2
:第二个源寄存器offset
:相对跳转的偏移量 BNE(Branch if Not Equal) 指令:
- 功能:比较两个寄存器的值,如果它们不相等,则跳转到指定的目标地址。
- 格式:
bne rs1, rs2, offset
rs1
:第一个源寄存器rs2
:第二个源寄存器offset
:相对跳转的偏移量 小于则置位(set less than, slt):
功能:将两个寄存器中的值进行比较,如果第一个寄存器的值小于第二个寄存器的值,则将目标寄存器设为1,否则设为0。
- 格式:
slt rd, rs1, rs2
rd
:目标寄存器rs1
:第一个源寄存器rs2
:第二个源寄存器
J指令(JUMP)
- 功能:无条件跳转到指定的目标地址。这个指令改变了程序的执行流程,立即将控制转移到指定的地址。
- 格式:
j offset
offset
:相对当前地址的跳转偏移量
2.3 Assembly language and High-level programming language
举个例子,有C++代码
其对应的汇编语言是
2.4 Three Instruction Formats
在汇编语言中,主要有三种指令格式:R型指令、I型指令和J型指令。每种格式有特定的结构,用于不同类型的操作。
2.4.1 R-type (Register) Instructions
- 目的:用于需要三个寄存器的算术和逻辑操作。
- 格式:
opcode
:指定指令类型的操作码。rs1
:第一个源寄存器。rs2
:第二个源寄存器。rd
:目标寄存器。funct3
和funct7
:附加功能代码,用于指定具体操作。
- 例子:
add rd, rs1, rs2
- 将
rs1
和rs2
中的内容相加,并将结果存储在rd
中。
- 将
2.4.2 I-type (Immediate) Instructions
- 目的:用于带有立即数(嵌入指令中的常数)的操作。
- 格式:
opcode
:指定指令类型的操作码。rs1
:源寄存器。rd
:目标寄存器。imm
:立即数(常数)。funct3
:附加功能代码。
- 例子:
addi rd, rs1, imm
- 将
rs1
和imm
的内容相加,并将结果存储在rd
- 将
2.4.3 J-type (Jump) Instructions
- 目的:用于修改程序控制流的跳转操作。
- 格式:
opcode
:指定指令类型的操作码。offset
:跳转偏移量。
- 例子:
j offset
- 跳转到通过将
offset
添加到当前程序计数器(PC)计算出的地址。
- 跳转到通过将
2.5 Five Addressing Modes
2.5.1 立即寻址(Immediate Addressing)
- 定义:操作数直接在指令中给出。
- 例子:
MOV AL, 5
- 这里,数值
5
直接被移动到寄存器AL
中。
- 这里,数值
2.5.2 直接寻址(Direct Addressing)
- 定义:操作数的地址在指令中明确给出。
- 例子:
MOV AX, [1234H]
- 该指令将内存地址
1234H
的值移动到寄存器AX
中。
- 该指令将内存地址
2.5.3 间接寻址(Indirect Addressing)
- 定义:操作数的地址存储在寄存器或内存位置中。
- 例子:
MOV AX, [BX]
- 内存位置
[BX]
中的值被移动到AX
中。
- 内存位置
2.5.4 索引寻址(Indexed Addressing)
- 定义:操作数的最终地址由寄存器内容与常数相加生成。
- 例子:
MOV AX, [SI+20H]
- 内存位置
[SI + 20H]
中的值被移动到AX
中。
- 内存位置
2.5.5 寄存器寻址(Register Addressing)
- 定义:操作数位于寄存器中,寄存器在指令中直接指定。
- 例子:
MOV AX, BX
BX
中的值被移动到AX
程序计数器PC在取指令时会自增4(每条指令是4字节),因此,下一条指令的地址是PC + 4。若当前指令需要进行跳转或分支,字地址偏移量则用于计算目标地址。具体来说,如果你有一个偏移量offset,在跳转指令中,你通常会将其左移2位(因为地址是字节为单位的),然后加到PC + 4上来获得目标地址。
3. Arithmetic Operations in Computers
本章主要讲解的知识点有,计算机是如何表示整数和进行整数运算的,以及浮点数的表示规范。本章在修考的考察非常精细,一定要理清每种数的表示。下面的例子都用8bit来表示并且给出对应的表示范围。
3.1 Integer Representation
本小节介绍无符号整数、原码、反码和补码的表示方法。
3.1.1 无符号整数(Unsigned Integer)
表示方法:
无符号整数只表示非负数,使用所有位表示数值,不用负号。8位无符号整数的取值范围是从0到255。
表示范围:
- 最小值:$0$
- 最大值:$2^8 - 1 = 255$
例子:
- 十进制数5的无符号表示:
00000101
- 十进制数255的无符号表示:
11111111
3.1.2 原码(Sign-Magnitude)
表示方法:
原码使用最高位作为符号位(0表示正数,1表示负数),其余位表示数值的绝对值。对于8位原码,正数的最高位是0,负数的最高位是1。因此原码有$+0$和$-0$两种表示方法,分别是$00000000$,$10000000$。
表示范围:
- 最小值:$-127$(
1111111
) - 最大值:$127$(
01111111
)
例子:
- 十进制数5的原码表示:
00000101
- 十进制数-5的原码表示:
10000101
3.1.3 反码(One’s Complement)
表示方法:
反码对正数和负数的表示方法稍有不同。正数的反码与原码相同,负数的反码是将其绝对值的原码的每一位取反(0变1,1变0)。因此反码有也有$+0$和$-0$两种表示方法,分别是$00000000$和$11111111$。
表示范围:
- 最小值:$-127$(
10000000
) - 最大值:$127$(
01111111
)
例子:
- 十进制数5的反码表示:
00000101
- 十进制数-5的反码表示:
11111010
(原码00000101
取反)
3.1.4 补码(Two’s Complement)
表示方法:
补码是最常用的负数表示方法。正数的补码与原码相同,负数的补码是其绝对值的原码取反后加1。补码的意义是消除原码和反码有2种零的表示方法,可以用于直接计算。计算机都是用补码进行整数加减运算。因为补码只有一种$0$的表示方法,因此负数的表示范围会比整数多一个。
补码非常爱考的一个点:$11111111$(-1), $10000000$(-128,因为超出8位范围), $00000000$(0)
表示范围:
- 最小值:$-128$(
10000000
),🚨重中之重 - 最大值:$127$(
01111111
)
例子:
- 十进制数5的补码表示:
00000101
- 十进制数-5的补码表示:
11111011
(原码00000101
取反后加1)
3.1.5 表示方法总结
表示法 | 最小值 | 最大值 | 示例 - 正数 | 示例 - 负数 | 0的表示个数 |
---|---|---|---|---|---|
无符号整数 | 0 | 255 | 00000101 (5) |
N/A | 1 |
原码 | -127 | 127 | 00000101 (5) |
10000101 (-5) |
2 |
反码 | -127 | 127 | 00000101 (5) |
11111010 (-5) |
2 |
补码 | -128 | 127 | 00000101 (5) |
11111011 (-5) |
1 |
3.2 符号位扩展与大小端编址
这几个考点经常出现在题目的开胃前菜,是很容易忽略的一个考点,切记切记。
3.2.1 符号位扩展(sign extension)
符号位扩展(sign extension) 是指在将带符号的数从较小的位数扩展到较大位数时,保持其数值的正负性。这个过程通常在处理数值的运算或数据类型转换时使用。
填充高位:
- 如果符号位为0,扩展时在左边填充0。
- 如果符号位为1,扩展时在左边填充1。
假设我们要将一个8位的补码数扩展到16位:
8位正数
- 原数:
00001010
(十进制10) - 扩展后:
00000000 00001010
(仍然是十进制10)
8位负数
- 原数:
11111010
(十进制-6) - 扩展后:
11111111 11111010
(仍然是十进制-6)
3.2.2 大端编址(Big-endian)和小端编址(Little-endian)
大端编址(Big-endian)和小端编址(Little-endian)是两种不同的数据存储方式,决定了多字节数据在内存中的排列顺序。
大端编址(Big-endian)
- 定义:在大端模式下,数据的高位字节存储在低地址,低位字节存储在高地址。
- 示例:对于32位整数
0x12345678
,其在内存中的存储顺序为:- 地址
0x00
:12
- 地址
0x01
:34
- 地址
0x02
:56
- 地址
0x03
:78
- 地址
小端编址(Little-endian)
- 定义:在小端模式下,数据的低位字节存储在低地址,高位字节存储在高地址。
- 示例:对于32位整数
0x12345678
,其在内存中的存储顺序为:- 地址
0x00
:78
- 地址
0x01
:56
- 地址
0x02
:34
- 地址
0x03
:12
- 地址
应用
- 大端:常用于网络协议(如TCP/IP),因为网络字节顺序采用大端。
- 小端:通常用于个人电脑(如x86架构的处理器)。
3.3 Arithmetic Operations Logic
参照数字电路笔记(🐎马不停蹄更新中)
3.4 IEEE754 Float Point Number
IEEE 754 单精度浮点数
IEEE 754 单精度浮点数总共32位:1位符号位(S),8位指数位(E),23位尾数位(F)。
- 符号位(S):0表示正数,1表示负数。
- 指数位(E):偏阶值(Bias)为127,即实际的指数值是存储的指数值减去127。
- 例如,如果指数部分存储的是10000001(二进制的129),实际的指数就是129-127=2。
- 尾数位(F):23位,表示小数部分。注意尾数部分隐含一个1,所以公式中写作
1.F
。
计算公式:
IEEE 754 双精度浮点数
IEEE 754 双精度浮点数总共64位:1位符号位(S),11位指数位(E),52位尾数位(F)。
- 符号位(S):0表示正数,1表示负数。
- 指数位(E):偏阶值(Bias)为1023,即实际的指数值是存储的指数值减去1023。
- 例如,如果指数部分存储的是10000000010(二进制的1026),实际的指数就是1026-1023=3。
- 尾数位(F):52位,表示小数部分。尾数部分隐含一个1,所以公式中写作
1.F
。
计算公式:
思考:为什么需要偏阶值(Bias)的存在?
偏阶(Bias)的存在主要是为了简化浮点数的比较和计算过程。以下是几个具体原因:
简化比较操作:
通过使用偏阶,所有可能的指数值都被映射到一个非负范围内,使得比较浮点数时可以简单地比较其指数部分的数值。例如,对于单精度浮点数,偏阶127确保了指数范围从 -127 到 128 被映射到 0 到 255。标准化表示:
使用偏阶可以确保指数部分总是一个非负数,从而使浮点数表示更为统一和标准化。这对于硬件电路设计和算法实现有很大的便利性。处理负指数:
偏阶允许使用非负整数来表示负指数,这对表示非常小的浮点数(接近于0)尤其有用。通过偏阶值,可以直接在指数部分进行加法和减法,而不需要额外处理负数。
举个例子,对于单精度浮点数,偏阶是127。这意味着:
- 实际指数为0时,存储的指数值是127。
- 实际指数为-1时,存储的指数值是126。
- 实际指数为+1时,存储的指数值是128。
这种设计大大简化了计算和比较过程,让浮点运算变得高效和准确。
4. Processor
MIPS是一种采取RISC的架构。MIPS 处理器的指令执行过程被分为五个主要阶段。 通过流水线的设计,MIPS 能够在每个时钟周期执行多个指令阶段,从而提升性能。以下是每个阶段的详细说明:
4.1 指令周期分解
取指令(IF,Instruction Fetch)
- 从指令存储器中读取当前指令,将其载入到指令寄存器(IR,Instruction Register)中。
- 将程序计数器(PC)更新为下一条指令的地址,一般为当前 PC 值加 4(因为每条指令长度固定为 4 字节)。
- 在这个阶段,通常也会将 PC 的值保存,以备后续的跳转或分支指令使用。
指令译码(ID,Instruction Decode)
- 解析指令的操作码(opcode),以确定指令类型和具体的操作。
- 从寄存器文件中读取操作数(源寄存器数据)。在 R 型指令中读取两个寄存器,而 I 型和 J 型指令则根据需求读取一个或不读取。
- 进行分支地址的计算(PC + 偏移量),以备可能的跳转或分支指令使用。
- 根据指令类型生成控制信号,控制信号会影响后续各个阶段的具体操作。
执行(EX,Execute)
- 算术或逻辑操作:如果是算术或逻辑指令,ALU(算术逻辑单元)执行相应操作,例如加法、减法、与或非等。
- 内存地址计算:若是加载或存储指令,ALU 计算内存访问的目标地址(基地址 + 偏移量)。
- 分支判断:若是分支指令,在 ALU 进行判断后决定是否采用分支地址。
- 这一阶段的结果(例如运算结果或目标地址)将传递给下一阶段。
访存(MEM,Memory Access)
- 加载指令:如果是加载指令(如
lw
),从内存中读取数据,并将其存入一个临时寄存器。 - 存储指令:如果是存储指令(如
sw
),将寄存器中的数据写入指定的内存地址。 - 其他指令在这一阶段不做任何操作,直接进入下一个阶段。
- 加载指令:如果是加载指令(如
写回(WB,Write Back)
- 将执行结果写回到寄存器文件中(如
R
型和I
型指令)。 - 对于加载指令,将从内存读取的数据写入目标寄存器。
- 此阶段完成后,处理器准备进入下一条指令的执行。
- 将执行结果写回到寄存器文件中(如
4.2 Pipeline Basics
在指令周期的五个阶段,可以把数据通路分为5个阶段,形成流水线(pipeline)。
根据上图,可以很直观的给出流水线相关计算公式:
注意⚠️:时钟周期取5个阶段中最慢的阶段作为时钟周期(木桶效应)
4.3 Pipeline Hazards
流水线冒险(Pipeline Hazards) 是在指令流水线中可能导致流水线停顿或性能下降的问题。主要有三种类型的流水线冒险:数据冒险、控制冒险和结构冒险。下面是对每种冒险的详细讲解:
4.3.1 数据冒险 (Data Hazard)
数据冒险是由于指令之间的数据依赖性导致的。例如,一条指令需要使用前一条指令的计算结果,如果在计算结果尚未写回寄存器时就使用该结果,流水线会因为缺少数据而停顿。常见的数据冒险有以下几种类型:
RAW(Read After Write,读后写):最常见的数据冒险类型。假设有指令 A 和指令 B,A 先于 B 执行。如果 B 读取 A 的结果而 A 还未写回寄存器,则会产生数据冒险。例如:
指令 B 在 A 的结果写回之前读取了 R1,造成数据冒险。
WAR(Write After Read,写后读):较少见,发生在指令先读取了寄存器的值,而后续指令修改了该寄存器的值。这种冒险通常在特定的流水线架构中才会发生。
WAW(Write After Write,写后写):如果流水线支持多发射或指令乱序执行,就可能发生这种冒险。即先有一条指令写一个寄存器,后续指令也写相同寄存器,但乱序执行导致后面的指令先写完。
解决方法:
- 数据转发 (Data Forwarding)或旁路(bypass):将未写回寄存器的数据直接转发给下一条指令的执行单元,从而避免等待写回寄存器的过程。
- 插入气泡 (Bubble):让流水线停顿一个或多个周期,直到所需数据准备就绪。
4.3.2 控制冒险 (Control Hazard)
控制冒险是因为分支指令或跳转指令的出现,使得流水线不能确定下一条要执行的指令,从而导致流水线停顿。分支指令可能改变程序执行的路径,而在确定跳转目标前,流水线并不知道应该继续取哪条指令。
例如,以下代码段中,当执行if
判断时,可能会发生控制冒险:
在判断 a > b
之前,流水线不知道下一步应该执行 do_something
还是 do_something_else
。
解决方法:
- 分支预测 (Branch Prediction):利用硬件预测分支指令的执行路径。现代处理器使用动态分支预测,根据历史执行情况预测下一步操作。
- 延迟槽 (Delay Slot):将分支指令后的指令提前执行,即使分支结果不确定也先执行下一条指令,从而减少停顿,但这种方法需要编译器支持。
- 分支目标缓冲区 (Branch Target Buffer, BTB):缓存分支指令的目标地址,减少分支跳转的停顿时间。
4.3.3 结构冒险 (Structural Hazard)
结构冒险是由于硬件资源的竞争导致的。在指令执行过程中,如果两个或多个指令同时需要访问同一硬件资源(例如,内存、寄存器或 ALU 单元),但硬件资源不能同时支持多个访问请求,则会产生结构冒险。
例如,如果处理器的内存访问和取指令共享相同的内存端口,且一个指令在内存中读取数据的同时,另一个指令需要从内存中取指令,就会产生结构冒险。
解决方法:
- 增加硬件资源:在流水线设计时,增加必要的硬件资源。例如,为指令存储和数据存储提供独立的内存端口,避免资源竞争。
- 流水线停顿:当资源不可用时,让流水线停顿一个周期,以等待资源的释放。
流水线在修考里面是非常非常重要的存在‼️我更希望后续用经典的例题讲解。等待更新⌛️
5. Memory
本章主要讲解存储器的层次和局部性原理。这一章和操作系统的重合度非常高,建议配合操作系统复习。本章的Cache映射和TLB映射在修考题中出现的频率非常高。
5.1 Memory Hierachy
如标题所见,这是计算机8大设计思想之一,采用层次结构将存储器主要分为4个层次,从顶层到底层依次是:寄存器,高速缓存,主存,外存。
5.1.1 Register
寄存器是 CPU 中的高速存储单元,用于暂时存储指令和数据,帮助 CPU 进行快速计算。它们是 CPU 中访问速度最快的存储器,因为它们直接位于 CPU 内核中,靠近运算单元(如算术逻辑单元,ALU)。
寄存器的特点
- 速度快:寄存器的访问速度比 Cache 和主存(RAM)都要快,能满足 CPU 的即时需求。
- 容量小:寄存器数量有限,通常每个寄存器只有几个字节或几十个字节的容量。
- 功能明确:寄存器根据用途被分为不同类型,每种寄存器在特定的指令或运算中起特定的作用。
常见的寄存器类型
- 通用寄存器:用于临时存储数据,支持基本的加、减、乘、除等运算。
- 程序计数器 (PC) :保存下一条要执行指令的地址,用于控制程序执行的顺序。
- 状态寄存器/标志寄存器:存储运算结果的状态信息,如是否为零、是否有溢出等,用于条件判断。
- 堆栈指针 (SP) :指向栈顶位置,用于函数调用和返回的管理。
- 地址寄存器:存储内存地址,用于快速访问特定的内存位置。
寄存器的作用
寄存器用于 CPU 执行指令时的临时数据存储,避免频繁访问较慢的内存单元。它们直接与 CPU 核心连接,确保计算快速、高效。
5.1.2 Cache
Cache(缓存)是位于 CPU 和主存(RAM)之间的高速存储器,用于缓解 CPU 与主存之间的速度差异。Cache 通过暂时存储常用的数据和指令,减少 CPU 访问主存的次数,从而提高整体运算速度。Cache 通常是由 SRAM(静态随机存取存储器) 组成的;SRAM是易失性存储器,尽管 SRAM 的数据不需要周期性刷新,但一旦电源关闭,存储在其中的数据就会丢失。这是因为 SRAM 的存储原理基于晶体管的电状态,当电源断开时,这些状态会消失。DRAM则有些许不同,需要定期刷新以维持数据,刷新操作会耗费一定时间和电力,但也是易失性存储器(volatile memory)。
Cache 的特点
- 访问速度快:Cache 的访问速度比主存快,仅次于寄存器,能快速提供数据。
- 层级结构:Cache 通常分为多级,包括 L1、L2 和 L3 缓存,分别位于不同的 CPU 层次上,L1 速度最快但容量最小,L3 容量最大但速度相对较慢。
- 自动管理:Cache 由硬件自动管理,CPU 根据访问数据的频率和规律自动将数据加载到 Cache 中,无需程序员手动干预。
Cache 的作用
Cache 主要用于存储 CPU 经常访问的数据或指令,以减少访问主存的延迟,提升 CPU 的执行效率。现代处理器通常会采用多级缓存结构,以平衡容量与速度之间的需求。
5.1.3 Main Memory
内存(Memory)一般称为主存(Main Memory),是计算机中用于临时存储数据和程序的部件,CPU 可以快速访问其中的数据。内存的主要作用是为 CPU 提供运行时的工作区,用于存放操作系统、应用程序和当前处理的数据。
内存的特点
- 速度适中:内存的访问速度介于 Cache 和硬盘之间。尽管比硬盘快很多,但比不上 CPU 内部的寄存器和缓存。
- 容量较大:内存容量一般较大,能够存储多个程序和大量数据,以满足系统的多任务需求。
- 易失性:内存(RAM)通常由 DRAM(动态随机存取存储器)组成。DRAM 由于容量大、成本相对较低,也是一种易失性存储器,断电后数据会丢失。
内存的作用
内存用于临时存放正在执行的程序和正在处理的数据,起到了 CPU 与硬盘之间的缓冲作用,使得系统运行更加高效。计算机在开机后,将程序从硬盘加载到内存中,CPU 再从内存中读取指令执行。
5.1.4 Secondary Memory
Secondary Memory(二级存储器)是计算机的外部存储,用于长期存储数据和程序,断电后数据不会丢失。常见的二级存储器包括硬盘驱动器 (HDD)、固态硬盘 (SSD)、光盘、以及 USB 闪存等。
辅助存储器的特点
- 非易失性(nonvolatile) :辅助存储器断电后仍能保留数据,适合长期存储文件、程序和系统数据。
- 大容量:相比于主存,辅助存储器容量更大,能够存储大量的数据和文件。
- 较慢的访问速度:辅助存储器的读写速度比内存慢,因此用于存储不需要频繁访问的数据。
辅助存储器的作用
辅助存储器用于存储计算机的操作系统、应用程序、用户文件和其他数据,提供数据的长期保存。计算机在启动时会将操作系统和相关程序从辅助存储器加载到内存中,以便 CPU 处理。
5.2 Principle of Locality
局部性原理(principle of locality)是计算机组成中的一个关键概念,用来描述程序在执行过程中,访问内存地址或存储单元的一种倾向。局部性原理主要分为以下两类:
时间局部性(Temporal Locality)
- 定义:如果一个数据被访问过,那么在不久的将来它很可能会再次被访问。
- 例子:在循环中多次访问某个变量。比如循环计数器,每次迭代都会访问该变量。
空间局部性(Spatial Locality)
- 定义:如果一个数据被访问过,那么它附近的数据也很可能会在不久的将来被访问。
- 例子:遍历数组时,逐个访问数组中的元素。比如访问数组
arr[0]
后,很可能访问arr[1]
。
局部性原理在设计计算机缓存(Cache)时尤为重要。因为缓存利用局部性原理,通过临时存储经常访问的数据或地址来减少访问主内存的次数,提高整体系统性能。
5.3 Cache’s Performance
Cache的主要考点围绕在命中率和缺失率的计算;在Cache映射中也有非常多的改进策略。
5.3.1 Cache Hit & Miss
Cache命中(Cache Hit)
- 定义:当处理器需要的数据在缓存中找到时,就称为Cache命中。命中率(Hit Rate)是命中次数占总访问次数的百分比。
- 示例:处理器需要读取一个数据块,如果它已经在缓存中,直接读取数据,避免了访问慢速的主内存,从而提高了整体性能。
Cache缺失(Cache Miss)
- 定义:当处理器需要的数据不在缓存中,必须从主内存中读取时,就称为Cache缺失。缺失率(Miss Rate)是缺失次数占总访问次数的百分比。
- 示例:处理器需要读取一个数据块,如果它不在缓存中,处理器需要从主内存中读取该数据块,并将其放入缓存中。这个过程比直接从缓存读取要慢得多。
命中率(Hit Rate)和缺失率(Miss Rate)的计算方法非常直观,分别是命中/缺失的次数占访存次数的比例。
5.3.2 命中时间、缺失代价和访存阻塞周期
命中时间(Hit Time)
- 定义:命中时间是指CPU在缓存中找到数据所需的时间,包括地址翻译、缓存访问和数据返回给处理器的时间。通常只有1个时钟周期长度。
- 计算:命中时间通常由缓存访问的硬件特性决定,具体时间取决于缓存的层级和设计。
缺失代价(Miss Penalty)
- 定义:缺失代价是指当数据不在缓存中,需要从较低层级的缓存或主存中加载数据所需的时间。
- 计算:缺失代价 = 低层级缓存或主存的访问时间 + 将数据传送到缓存中的时间。
访存阻塞周期(Memory Stall Cycles)
- 定义:访存阻塞周期是指由于缓存缺失导致处理器需要等待数据加载而停顿的周期数。
- 计算:访存阻塞周期 = 缺失率 × 缺失代价
假设:
- 命中时间为1个周期。
- 缺失代价为50个周期。
- 缺失率为5%。
那么:访存阻塞周期 = 5% × 50个周期 = 2.5个周期。
普适性的衡量会给上一个缺失率(Miss Rate), 那么
5.4 Cache的三种映射方式
1. 直接映射(Direct Mapped Cache)
- 定义:每个内存块都映射到缓存的一个特定位置。缓存中的每个位置可以存储多个内存块,但在任意时间只能存储一个。
- 优点:实现简单且成本低。
- 缺点:冲突较多,即不同内存块可能会映射到同一个缓存位置,导致频繁替换。
2. 全关联映射(Fully Associative Cache)
- 定义:内存块可以映射到缓存的任何位置。缓存中的每个位置都可以存储任何内存块。
- 优点:减少了冲突,因为任何内存块都可以放到任何缓存位置。
- 缺点:实现复杂且成本高,需要比较所有缓存位置以找到匹配的块。
3. 组关联映射(Set Associative Cache)
- 定义:缓存分成多个组,每个组包含若干个位置。内存块首先映射到某个组,然后可以存储在该组的任意位置。
- 优点:在复杂度和性能之间取得平衡,减少了直接映射中的冲突,同时实现比全关联映射更简单。
- 缺点:组的选择可能会导致一些复杂度,但总的来说,比全关联映射低。
5.4.1 直接映射
映射规则:Cache块号 = 内存块号 % Cache块数
比如,Cache共有8块,内存的十号块映射在Cache的块号是:10 % 8 = 2
直接映射缓存(Direct Mapped Cache)内存地址通常被分成三个字段:块内字节偏移、索引位和标记位。
1. 块内字节偏移(Block Offset)
- 定义:块内字节偏移用于定位一个块中的具体字节。
- 计算:偏移位的数量取决于块的大小。例如,如果块大小为4个字节,则需要2位来表示块内字节偏移。
- 例子:对于一个块大小为4个字节的缓存,偏移位可能是
00
、01
、10
、11
。
- 例子:对于一个块大小为4个字节的缓存,偏移位可能是
2. 索引位(Index Bits)
- 定义:索引位用于定位缓存中的具体行(块)。
- 计算:索引位的数量取决于缓存的行数。例如,如果缓存有8行,需要3位索引位来标识具体的行。
- 例子:对于一个有8行的缓存,索引位可能是
000
到111
。
- 例子:对于一个有8行的缓存,索引位可能是
3. 标记位(Tag Bits)
- 定义:标记位用于区分不同内存块,它们映射到同一个缓存行。
- 计算:标记位的数量 = 内存地址总位数 - 索引位数量 - 块内字节偏移位数量。
- 例子:假设内存地址总位数为16位,块大小为4字节(2位块内偏移),缓存有8行(3位索引),那么标记位数量为11(16-3-2=11)。
示例 :假设有一个16位的内存地址,缓存有8行,每行块大小为4字节。
内存地址:1010110010101110
- 块内字节偏移:2位(最右边的2位),
10
表示块内的某个字节。 - 索引位:3位(次右边的3位),
110
表示缓存的具体行。 - 标记位:11位(剩余左边的位),
10101100101
用于区分不同的内存块。
内存地址分解:
- 标记位(Tag):
10101100101
- 索引位(Index):
110
- 块内字节偏移(Block Offset):
10
有效位是缓存系统中的一个重要概念,用于指示缓存块中的数据是否有效。具体来说,有效位帮助确定当前缓存块是否包含可以被处理器使用的有效数据。
- 指示数据有效性:有效位为1表示缓存块中的数据有效且可用;为0表示缓存块中的数据无效,可能未被使用或需要更新。
- 管理缓存块:当一个新的内存块被加载到缓存时,会设置有效位为1。当缓存块的数据被替换或失效时,有效位被设置为0。
实现原理
- 检查有效位:每次缓存访问时,首先检查有效位。如果有效位为0,即使缓存地址匹配,数据也不被使用,因为它无效。
- 缓存替换:在缓存替换时,新的数据块写入缓存,并将对应的有效位设置为1。被替换的数据块的有效位通常被设置为0,表示数据无效。
示例
假设一个缓存系统有4个块,每个块都有一个有效位:
- 块0:有效位=1(数据有效)
- 块1:有效位=0(数据无效)
- 块2:有效位=1(数据有效)
- 块3:有效位=0(数据无效)
当处理器访问一个地址时,它首先检查缓存块的有效位。如果有效位为1,它会进一步检查地址是否匹配;如果有效位为0,则直接从主存获取数据。
5.4.2 直接映射缺失的3C模型
1. 冷缺失(Cold Miss)
- 定义:也称为强制缺失或首次缺失。这种情况发生在缓存首次加载数据块时,因为缓存中还没有存储该数据块。
- 示例:程序第一次访问一个数据块时,缓存中还没有该数据块,导致冷缺失。
2. 容量缺失(Capacity Miss)
- 定义:由于缓存容量有限,即使缓存中所有块都被充分利用,仍然无法容纳所有需要的数据块,从而导致缺失。
- 示例:程序需要访问的数据块数量超过了缓存容量,导致一些较早访问的数据块被驱逐,再次访问时需要重新加载。
3. 冲突缺失(Conflict Miss)
- 定义:也称为干扰缺失。这种情况发生在直接映射缓存中,不同的数据块映射到同一个缓存块位置,导致频繁替换,即使缓存容量足够也会出现缺失。
- 示例:两个或多个数据块映射到同一个缓存位置,导致数据块被替换,从而产生冲突缺失。
直接映射在应对上面的Cache缺失的时候有三种处理方式:
1.写直达(Write Through):
- CPU同时向Cache和主存写入数据
- 实现简单,但写入速度慢
- 保持了Cache和主存的一致性
写缓冲(Write Buffer):
- CPU先写入Cache
- 数据暂存在写缓冲区
- 后台异步写入主存
- 提高了写入效率
写回(Write Back):
- CPU只写入Cache
- 使用脏位(Dirty Bit)标记修改
- 仅在数据被替换时才写回主存
- 最高效但实现复杂
5.4.3 多级缓存机制
多级缓存(Multilevel Cache)是一种在计算机系统中提高处理器访问数据速度的技术,通常将缓存分为多个层次,如 L1、L2 和 L3 缓存。这些缓存层次的设置能够平衡存取速度、容量和成本,确保 CPU 能更快地获取需要的数据。
多级缓存的主要特点
L1缓存(一级缓存):通常集成在 CPU 内核中,速度最快,容量较小(一般为几十KB)。L1缓存分为指令缓存(I-Cache)和数据缓存(D-Cache),分别用于存储指令和数据,帮助 CPU 快速访问常用数据。
L2缓存(二级缓存):相比 L1 缓存稍慢,但容量更大(几百KB到几MB),通常也集成在处理器中。L2 缓存为 L1 提供数据支援,命中率高,进一步减少了 CPU 对主存(RAM)的访问需求。
L3缓存(三级缓存):用于共享多个 CPU 内核的数据,容量较大(几MB到几十MB),但访问速度比 L2 缓慢。L3缓存作为所有内核的公共缓存,提升了多核处理器的并行性能。
工作原理:CPU 会首先查找 L1 缓存,如果未命中则依次查找 L2 和 L3 缓存,直到最后访问主存。这样的分级缓存结构可以减少主存访问次数,从而提高系统性能。
优点:多级缓存结构能显著降低数据访问延迟,提高处理器的执行效率;而且它能有效利用缓存的层级特性,合理分配存取速度和容量。
多级缓存背景下的平均 CPI 计算
在多级缓存系统中,平均每条指令的周期数(CPI,Cycles Per Instruction)可以通过考虑每一级缓存的缺失率和缺失代价来计算。这里的缺失率指的是每一级缓存未命中(缺失)的概率,缺失代价则是缓存未命中时所需要的额外处理周期数。平均 CPI 的计算可以按以下步骤进行。
假设:
- $\text{CPI}_{\text{ideal}}$ 是理想情况下(没有缓存缺失)的 CPI,即不考虑缓存缺失时每条指令的周期数。
- L1、L2、L3 分别是一级、二级和三级缓存。
- $\text{MR}_n$ 表示第 $n$ 级缓存的缺失率(Miss Rate)。
- $\text{MC}_n$ 表示第 $n$ 级缓存的缺失代价(Miss Penalty),即在第 $n$ 级缓存缺失时需要的额外周期数。
平均 CPI 的计算可以分为以下步骤:
L1 缓存的贡献:L1 缓存缺失的情况下,需要访问 L2 缓存,因此 L1 缓存的平均贡献为:
$\text{CPI}_{\text{L1}} = \text{MR}_{\text{L1}} \times \text{MC}_{\text{L1}}$ L2 缓存的贡献:L1 缓存缺失时会访问 L2 缓存,如果 L2 缓存也缺失,则需要访问 L3 缓存。L2 缓存的平均贡献为:
$\text{CPI}_{\text{L2}} = \text{MR}_{\text{L1}} \times \text{MR}_{\text{L2}} \times \text{MC}_{\text{L2}}$ L3 缓存的贡献:当 L1 和 L2 都缺失时,才会访问 L3 缓存。如果 L3 缓存也缺失,就会访问主存,因此 L3 缓存的平均贡献为:
$\text{CPI}_{\text{L3}} = \text{MR}_{\text{L1}} \times \text{MR}_{\text{L2}} \times \text{MR}_{\text{L3}} \times \text{MC}_{\text{L3}}$ 主存的贡献:当 L1、L2 和 L3 缓存都缺失时,才会访问主存。因此主存的平均贡献为:
$\text{CPI}_{\text{memory}} = \text{MR}_{\text{L1}} \times \text{MR}_{\text{L2}} \times \text{MR}_{\text{L3}} \times \text{MC}_{\text{memory}}$
把各级缓存和主存的贡献相加,再加上理想情况下的 CPI,得到总的平均 CPI:
展开后,即:
- 每一级缓存的贡献都是基于它前一级缓存的缺失率和自身的缺失代价递归计算的。
- 这个公式综合了各级缓存的缺失率和缺失代价,提供了一个较为准确的平均 CPI 估计。
5.5 Virtual Memory
虚拟存储器通过地址映射机制,将操作系统使用的虚拟地址转换为不同的物理地址。这个过程涉及多层缓存和映射关系,例如:
- Cache 将处理器寄存器访问的地址映射到内存中的物理地址,以加速数据读取。
- 主存(内存)充当磁盘的缓存,从磁盘读取数据时,常用的数据会保存在内存中以加快访问速度。
- TLB(快表)是页表的缓存,用于快速查找虚拟地址到物理地址的映射,减少页表查找的开销。
将虚拟地址转换为物理地址的过程通常涉及几个关键步骤:
地址生成:当程序访问某个内存位置时,它会生成一个虚拟地址。
页号和偏移量:虚拟地址被分为两个部分:页号和偏移量。页号用于识别该地址所属的页,而偏移量则指定了在该页内的具体位置。
快速表 (Translation Lookaside Buffer, TLB):在检查页表之前,系统首先检查快速表 (TLB),这是一个小而快速的缓存,存储最近的虚拟到物理地址的转换。如果在 TLB 中找到转换(TLB 命中),则可以快速检索物理地址。
页表查找:如果在 TLB 中未找到转换(TLB 未命中),系统将访问页表。页表包含条目,将每个虚拟页号映射到内存中相应的物理页框号。
物理地址形成:一旦访问了页表,就可以获得物理页框号。然后,物理地址通过将该页框号与原虚拟地址中的偏移量结合来形成。
访问内存:系统现在使用物理地址访问 RAM 中的数据。
页面缺失处理:如果所需的页面不在内存中(页面缺失),操作系统将从磁盘存储中检索该页面,并相应地更新页表。
关于虚拟地址和物理地址还有更多的考点内容,比如各种替换策略和写策略,这里的内容我更愿意放到操作系统的对应章节来讲解😈马不停蹄更新中🐎
完结撒花🎉
催更|辅导|私塾兼职|联系偷偷:LifeGoesOn_Rio