目录

1. 建立数据通路

1.1 指令周期(Instruction Cycle)

1.1.1 指令执行步骤

1.1.2 指令周期

1.2 建立数据通路

1.2.1 数据通路的组成

1.2.2 控制器

2. 面向流水线的指令设计

2.1 单指令周期处理器

2.1.1 单指令周期处理器概念

2.1.2 单指令周期处理器的问题

2.2 现代处理器的流水线设计

2.2.1 流水线的概念

2.2.2 流水线的好处

2.2.3 流水线的问题

3. 流水线

3.1 再论流水线利弊

3.1.1 CPU主频必须随流水线深度同步提升

3.1.2 流水线提升指令吞吐率

3.2 冒险和乱序执行

3.2.1 冒险问题

3.2.2 乱序执行

4. 冒险和预测

4.1 结构冒险

4.1.1 结构冒险含义

4.1.2 结构冒险实例

4.1.3 通过增加资源解决结构冒险

4.2 数据冒险

4.2.1 数据冒险含义

4.2.2 三种不同的数据依赖关系

4.3 通过流水线停顿解决数据冒险

4.3.1 流水线停顿的引入

4.3.2 流水线停顿的实现

4.4 NOP操作和指令对齐

4.4.1 不同指令的流水线阶段

4.4.2 插入NOP操作避免结构冒险

4.5 操作数前推

4.5.1 流水线停顿方案

4.5.2 操作数前推方案

4.5.3 对操作数前推的理解

4.6 乱序执行

4.6.1 乱序执行的引入

4.6.2 乱序执行的实现

4.7 控制冒险

4.7.1 取指阶段不停顿的假设

4.7.2 控制冒险的含义

4.8 缩短分支延迟

4.8.1 思路

4.8.2 硬件保障

4.8.3 效果

4.9 静态分支预测

4.10 动态分支预测

4.10.1 一级分支预测

4.10.2 双模态分支预测

4.11 分支预测性能影响实例

5. Superscalar和VLIW

5.1 多发射与超标量

5.2 VLIW技术

6. SIMD

6.1 超线程技术

6.1.1 引入背景

6.1.2 超线程实现线程级并行

6.2 SIMD技术

7. 异常和中断

7.1 软硬件组合处理

7.1.1 概述

7.1.2 异常处理流程

7.2 异常的分类

7.3 异常处理与函数调用的区别

8. CISC和RISC

8.1 特征概述

8.2 微指令架构

8.2.1 Intel使用CISC架构的原因

8.2.2 Intel引入微指令架构

9. 理解虚拟机

9.1 概述

9.2 解释型虚拟机

9.3 全虚拟化虚拟机

9.3.1 概述

9.3.2 Hypervisor的引入

9.3.3 Type-1 & Type-2型虚拟机

9.4 操作系统级虚拟机

9.4.1 Type-1型虚拟机的缺点

9.4.2 基于Docker的容器技术


1. 建立数据通路

1.1 指令周期(Instruction Cycle

1.1.1 指令执行步骤

1. Fetch(取得指令)

从PC寄存器中取得指令地址,根据指令地址从内存中将指令加载到指令寄存器中,然后PC寄存器自增,好在未来执行下一条指令

2. Decode(指令译码)

解析指令寄存器中的指令,得出要进行什么样的操作,具体要操作哪些寄存器、数据或者内存地址

3. Execute(执行指令)

实际运行指令,进行算数逻辑运算、数据传输或者直接的地址跳转

1.1.2 指令周期

指令周期可以理解为一个永不停歇的"Fetch - Decode - Execute"循环

说明1:指令周期的不同步骤在不同的组件中完成

说明2:指令周期与机器周期、时钟周期的关系

首先说明下机器周期和时钟周期的含义,

① 机器周期(Machine Cycle)也叫CPU周期,是指执行指令周期中一个动作的时间中耗时最长的时间(详见流水线小节)

② 时钟周期(Clock Cycle)是CPU主频的倒数

三个周期之间的关系如下图所示,

一个机器周期由多个时钟周期组成,一个指令周期由多个机器周期组成

需要注意的是,指令译码由逻辑电路完成,不需要时钟周期

1.2 建立数据通路

1.2.1 数据通路的组成

1. 操作元件,也叫组合逻辑元件(Combinational Element),就是ALU,他的功能就是在特定的输入下,根据组合电路的逻辑,生成特定的输出

2. 存储元件,也叫状态元件(State Element),比如在计算过程中使用的寄存器

通过数据总线的方式,将操作元件和存储元件连接起来,就可以完成数据的存储、处理和传输了,这就是所谓的建立数据通路

1.2.2 控制器

1. 控制器的逻辑并不复杂,可以简单理解为重复执行取指和译码操作,然后产生控制信号将计算交给ALU处理

2. 控制器的电路特别复杂,因为所有CPU支持的指令,都会在控制器中被解析成不同的输出信号。以目前的Intel CPU为例,他支持2000条以上的指令,也就是说,控制器输出的控制信号至少有2000种不同的组合

2. 面向流水线的指令设计

2.1 单指令周期处理器

2.1.1 单指令周期处理器概念

1. 单指令周期处理器(Single Cycle Processor)是指在一个时钟周期内,处理器正好能处理一条指令,也就是CPI为1

2. CPU的时钟周期是固定的,但是指令的复杂程度是不同的(e.g. 加法指令和乘法指令),所以实际一条指令执行的时间是不同的

为了实现所有指令在一个时钟周期内都能完成,只能将时钟周期设置成和执行时间最长的指令一样

2.1.2 单指令周期处理器的问题

1. 简单指令浪费指令周期

2. 无法将时钟频率设置得太高,因为太高的话,有些复杂的指令就无法在一个时钟周期内完成

2.2 现代处理器的流水线设计

2.2.1 流水线的概念

1. CPU的指令周期本身就分为多个步骤,比如取指、译码、执行

2. 这3个不同的单元可以并行工作,在一个阶段的电路完成对应的任务后,不需要等待整个指令执行完成,而是可以直接执行下一条指令的对应阶段

3. 时钟周期无需设置成整条指令执行的最长时间,而是一个步骤的最长执行时间,因此可以提升CPU主频

4. 对于耗时较长的步骤,可以进一步拆分,增加流水线的级数。现代的ARM或者Intel CPU,流水线级数已经达到14级

2.2.2 流水线的好处

虽然将一条指令的执行拆分到不同的时钟周期,但是可以将CPU主频设置得更高。因为现在不再需要确保最复杂的指令在一个时钟周期内完成,而是只要保障一个最复杂的流水线级的操作能在一个时钟周期内完成即可

2.2.3 流水线的问题

1. 每级流水线均有开销

每级流水线的输出均要放到流水线寄存器(Pipeline Register),然后在下一个时钟周期交给下一级流水线级处理,所以流水线级数越多,这种开销越大。因此不能无限制地增加流水线,否则性能的瓶颈就会出现在这种开销上

2. 无法确保指令执行顺序

因为指令的执行不再是顺序地一条条执行,而是在上一条执行到一半的时候,下一条已经启动执行,如果下一条指令更简单,所需流水线级数更少,就会比上一条指令先执行完成

3. 流水线

3.1 再论流水线利弊

3.1.1 CPU主频必须随流水线深度同步提升

1. 随着流水线的引入,每个细分的流水线步骤变得简单,而时钟周期的设置只要能覆盖耗时最长的流水线步骤即可,所以时钟频率可以设置得更高(可能性角度)

2. 但是从另一方面来说,提升时钟频率也是引入流水线之后的被动行为。因为在增加流水线深度的情况下,如果主频不变,其实降低了CPU的性能(必要性角度)

3. 31级流水线的3GHz主频的CPU,和11级流水线的1GHz主频的CPU,性能是差不多的。事实上,因为每级流水线有相应的开销,此时更深的流水线的性能可能更差一些

说明1:示例解析

对于31级流水线,一条指令被拆分为31个阶段,就需要最长31个时钟周期才能完成一条指令;相应地,对于11级流水线,只需要最长11个时钟周期就能完成一条指令。所以在主频相同的情况下,如果单纯增加流水线级数,其实降低了单条指令的响应时间

所以只有相应提升时钟周期,CPU在指令的响应时间这个指标上才能保持和原来的性能一致

说明2:CPU主频的提升会带来功耗的提升

3.1.2 流水线提升指令吞吐率

虽然单纯增加流水线级数会降低指令的响应时间,但是可以增加运行多条指令时的吞吐量。下面给出一个示例,假设要顺序运行如下3条指令,

1. 一条整数加法指令,需要200ps

2. 一条整数乘法指令,需要300ps

3. 一条浮点数乘法指令,需要600ps

在没有流水线的单指令周期CPU上,时钟周期需要设置为600ps,那么执行上述3条指令就需要1800ps

假设采用6级流水线,每级流水线需要100ps(这里其实就隐含了时钟周期为100ps,主频是没有流水线时的6倍),那么执行上述3条指令只需要800ps

可见当采用了6级流水线,且时钟频率提升6倍之后,虽然指令的响应时间没有变化,但是提升了指令吞吐率

3.2 冒险和乱序执行

3.2.1 冒险问题

采用流水线并提升主频之后,响应时间不变,吞吐率提升的结论是在理想状态下得到的。在实际程序的执行中,并不一定能够做到

典型的场景就是依赖问题,假设要执行如下3条指令,

int a = 10 + 5;int b = a * 2;float c = b * 1.0f;

由于存在依赖关系,后续指令要等待之前的指令执行完毕才能继续执行,所以总耗时为200 + 300 + 600 = 1100ps

这种依赖问题,就是冒险(Hazard)问题。示例中为数据冒险,也就是数据层面的依赖。在实际应用中,还有结构冒险、控制冒险等依赖问题

3.2.2 乱序执行

1. 乱序执行的就是把后面没有依赖关系的指令放到前面运行

2. 对于深度为N的流水线,要充分使用CPU性能,就需要确保同时运行的N条指令没有依赖关系。但是随着流水线深度的增加,还是很困难的

因为随着流水线级数的增加,同时在流水线中的指令数就会增加,也就更容易出现指令间的相互依赖

4. 冒险和预测

4.1 结构冒险

4.1.1 结构冒险含义

1. 结构冒险的本质是一个硬件层面的资源竞争问题,也就是一个硬件电路层面的问题

2. CPU在同一个时钟周期,同时在运行两条计算机指令的不同阶段,但是这两个不同的阶段,可能会用到同样的硬件电路

4.1.2 结构冒险实例

以图中所示的5级流水线为例,在第1条指令执行到访存(MEM)阶段时,第4条指令执行到取指(Fetch)阶段,访存和取指操作均要读取内存

而内存只有一个地址译码器作为地址输入,在一个时钟周期内只能读取一条数据,因此无法同时满足访存和取指操作,此时便构成了结构冒险

4.1.3 通过增加资源解决结构冒险

对于上述实例,可以通过增加资源的方式解决结构冒险,一种直观的方案就是将内存分为两部分,分别存放指令和数据,并且有各自的地址译码器

聪明的小伙儿肯定想到了冯诺依曼结构与哈佛结构,但是在现代CPU中,仍然使用了冯诺依曼结构,没有在内存层面进行拆分。实际的解决方案是在Cache部分进行了拆分,将Cache分成指令缓存(Instruction Cache)和数据缓存(Data Cache),从而解决结构冒险问题

说明:现代CPU为何不使用哈佛结构

如果采用哈佛结构的话,对程序指令和数据需要的内存空间就无法根据实际情况进行动态分配了,虽然解决了资源冲突问题,但是也失去了灵活性

4.2 数据冒险

4.2.1 数据冒险含义

数据冒险就是同时在执行的多个指令之间有数据依赖的情况

4.2.2 三种不同的数据依赖关系

4.2.2.1 先写后读

示例代码如下,

int main(void){int a = 1;int b = 2;a = a + 2;b = a + 3;return 0;}

代码反汇编如下,

1. "向a所在的内存加2"的操作必须在"取出变量a"的操作之前完成,这就是先写后读的数据依赖。如果这个顺序不能保证,程序就会出错

2. 先写后读的依赖关系,一般称之为数据依赖(Data Dependency)

4.2.2.2 先读后写

示例代码如下,

int main(void){int a = 1;int b = 2;a = b + a;b = a + b;return 0;}

代码反汇编如下,

1. "取出变量b的值"操作必须在"向b所在的内存加a的值"操作之前完成,否则程序出错

2. 先读后写的依赖关系,一般称之为反依赖(Anti-Dependency)

4.2.2.3 写后再写

示例代码如下,

int main(void){int a = 1;a = 2;return 0;}

代码反汇编如下,

1. 要确保两次写入的先后顺序,否则程序出错

2. 写后再写的依赖关系,一般称之为输出依赖(Output Dependency)

4.3 通过流水线停顿解决数据冒险

4.3.1 流水线停顿的引入

1. 除了读之后再读之外,对同一个寄存器或者内存地址的操作,都有明确强制的顺序要求。而这个顺序操作的要求,会给流水线带来很大的挑战。因为流水线架构的核心,就是在前一个指令还没有结束时,后面的指令就要开始执行

2. 解决数据冒险的最简单的办法就是使用流水线停顿(Pipeline Stall),也就是如果判断出会触发数据冒险(即后面执行的指令对前面执行的指令有数据层面的依赖),让整个流水线停顿一个或多个周期

说明:判断数据冒险的依据

在进行指令译码时,会拿到对应指令所需要访问的寄存器和内存地址,所以可以判断出该指令是否会触发数据冒险

4.3.2 流水线停顿的实现

由于时钟信号不会停歇,所以流水线停顿并不是真的让流水线不执行,而是在执行后面的操作步骤前插入NOP操作

说明1:NOP指令的插入就像在流水线的某个步骤给了一个空气泡,所以流水线停顿也被称作流水线冒泡(Pipeline Bubble)

说明2:在采用流水线停顿的解决方案时,不仅要在当前指令中插入NOP操作,所有后续指令也要插入相应的NOP操作,也就是要停顿,后续的所有指令都要停顿,步调一致(否则就会出现同一时刻,不同流水线进入同一个步骤的情况,也就是发生了结构冒险)

说明3:流水线停顿的方案,是以牺牲CPU性能为代价的,在最差的情况下,流水线架构的CPU会退化为单指令周期的CPU

4.4 NOP操作和指令对齐

4.4.1 不同指令的流水线阶段

以32位mips指令及其5级流水线为例,不同指令并不一定在所有的流水线阶段均有操作

以上图为例,STORE指令没有写回阶段,而ADD指令没有访存阶段

4.4.2 插入NOP操作避免结构冒险

有些指令没有对应的流水线阶段,但是我们不能跳过对应的阶段直接执行下一阶段,否则可能导致在某个时钟周期内,不同指令处于相同的流水线阶段,相当于触发了一次结构冒险

例如ADD指令跳过访存阶段直接执行写回阶段,就会与LOAD指令发生结构冒险

在实践当中,各个指令不需要的阶段并不会直接跳过,而是会运行一次NOP操作,实现指令对齐

4.5 操作数前推

假设要执行如下2条指令,

add $t0, $s2, $s1add $s2, $s1, $t0

由于第2条指令中的操作数来自寄存器t0,而t0的值来自第1条指令的计算结果,所以两条指令存在数据依赖关系

4.5.1 流水线停顿方案

由于第2条指令的执行依赖第1条指令的结果,所以在第2条指令的译码阶段之后加入2个NOP,等待第1条指令的写回阶段结束,才开启第2条指令的执行阶段

4.5.2 操作数前推方案

由于在第1条指令的执行阶段已经得到的运算结果,无需先写回寄存器,之后第2条指令再从寄存器中读取,而是可以直接将执行的结果传输给下一条指令的ALU,这样就减少了2次NOP操作

4.5.3 对操作数前推的理解

1. 上述解决方案就被称作操作数前推(Operand Forwarding)或者操作数旁路(Operand Bypassing),也可以称作操作数转发

2. 操作数转发是这个技术的逻辑含义,也就是将第1条指令的执行结果直接转发给第2条指令的ALU

3. 操作数旁路则是技术的硬件含义,为了实现转发,在CPU硬件层面需要单独的信号传输线路,使得ALU的计算结果能够重新回到ALU的输入中

说明:操作数前推有时会和流水线停顿组合使用

如上图中所示,由于LOAD指令要在访存阶段才能得到数据,所以ADD指令仍然需要停顿一个时钟周期

也就是说,操作数前推并不能完全避免流水线停顿,只是能缓解

4.6 乱序执行

4.6.1 乱序执行的引入

假设要执行如下3条语句,

a = b + c;d = a * e;x = y * z;

虽然d的计算需要等待a的计算结果,所以需要流水线停顿与操作数前推,但是x的计算不依赖前2条语句,所以在第2条语句等待操作数时,可以先计算第3条语句

说明:如果只是使用流水线停顿技术,那么第3条指令的执行阶段之前,也要增加2个NOP,但是这是完全没有必要的,因为没有数据依赖关系

4.6.2 乱序执行的实现

引入乱序之后之后的5级流水线如下图所示,

下面说明引入乱序执行之后的流水线工作流程,

1. 在取指和译码阶段,乱序执行的CPU和其他使用流水线架构的CPU是一样的,为顺序执行

2. 在译码之后,CPU会进行指令分发,将指令发送到保留站。这些指令不会立刻执行,而是要等待他们所依赖的数据

3. 一旦指令依赖的数据到齐,指令就可以提交到后面的功能单元(Funtion Unit,FU),其实就是ALU去执行

我们有很多功能单元可以并行运行,但是不同的功能单元能够执行的指令并不相同(e.g. 加法单元、乘法单元)。这里隐含了一个概念,即ALU并不是一个完整的黑盒子,而是不同的功能模块

4. 指令执行的阶段完成后,并不能立刻将结果写回寄存器,而是将结果存放在重排序缓冲区。在重排序缓冲区中,CPU会按照取指的顺序对指令的结算结果重新排序。只有排在前面的指令都已经完成了,才会提交指令,完成整个指令的运算结果

个人:从这里可以看出,CPU在乱序执行时,是在处理好依赖关系之上的乱序,依赖关系本身是不会乱的;而且最后指令的提交是按照取指顺序排列的

5. 实际的指令的计算结果,并不是直接写入内存或者Cache,而是先写入存储缓冲区,最终才会写入Cache或内存中

说明1:只有CPU内部指令的执行是乱序的,但是最终指令计算结果写入寄存器或内存之前,依然会进行一次排序,以确保所有指令在外部看来仍然是有序完成的

这也被称作强内存模型(Strong Memory Model),即保障内存数据访问顺序

说明2:乱序执行技术,就好像在指令的执行阶段提供一个"线程池",指令不再是顺序执行的,而是根据池中的资源(主要是FU资源,也就是结构冒险),以及各个任务是否可以执行(主要是数据依赖关系,也就是数据冒险),进行动态调度

说明3:现代CPU的运行速度远超过内存访问速度,如果完全采用顺序执行的方式,很多时间肯定会浪费在前面指令等待获取内存数据,CPU不得不加入NOP指令进行空转

而乱序执行及Cache的引入,弥补了CPU和内存之间的性能差异,同时充分利用了较深流水线的并发性

4.7 控制冒险

4.7.1 取指阶段不停顿的假设

到目前为止所介绍的内容中,取指和译码阶段均没有流水线停顿,这里是假设所有指令都是顺序加载执行的

但是一旦指令中出现分支结构(e.g. 条件语句、循环语句),指令就有可能不是顺序加载执行,而是要等待上一条指令结束后,才能取到正确的指令

4.7.2 控制冒险的含义

为了确保能取到正确的指令,而不得不进行等待延迟的情况,就是控制冒险(Control Hazard)

以如下的汇编语言为例,说明跳转指令对流水线的影响

当运行到地址为0x46的jne 51指令时,之后的mov指令是否应该顺序加载执行,在流水线的译码阶段是无法知道的,要等jne指令执行完成,更新了PC寄存器之后,才能知道是顺序执行地址为0x48的指令,还是跳转执行地址为0x51的指令。相应的流水线状况如下,

假设jne指令要到写回阶段才会更新PC寄存器,那么下一条指令就要在此之后才能正确取指

4.8 缩短分支延迟

4.8.1 思路

缩短分支延迟的思路和操作数前推类似,就是在硬件电路层面将计算结果更早地反馈到流水线中,以减少后续指令的等待时间

4.8.2 硬件保障

对于分支跳转指令,由于在译码阶段就能获得条件寄存器,所以可以增加电路,在译码阶段就将结果反馈到流水线中

4.8.3 效果

在缩短分支延迟之后,只需要等待在译码阶段之后,就可以正确取指

4.9 静态分支预测

1. 静态分支预测的策略是"假装分支不发生",即默认顺序加载指令

2. 如果预测错误,则丢弃已经进行的流水线操作,并清除影响

3. 本质上,所有分支预测技术都是赌概率

4.10 动态分支预测

4.10.1 一级分支预测

1. 一级分支预测就是直接用当前分支的比较情况,来预测下一次分支的比较情况

2. 一级分支预测也称作1比特饱和计数(1-bit saturating counter)

3. 如果预测错误,同样需要清除流水线

4.10.2 双模态分支预测

1. 双模态分支预测使用2个比特,构成状态机进行预测

2. 并不是更复杂的算法预测效果就一定更好

说明:现代CPU中的晶体管数量越来越多,构造越来越复杂,实际并不是用来实现"计算"这个核心功能,而是用来实现乱序执行、进行分支预测以及增加高速缓存

4.11 分支预测性能影响实例

public class BranchPrediction {public static void main(String[] args) {long start = System.currentTimeMillis();for (int i = 0; i < 100; i++) {for (int j = 0; j < 1000; ++j) {for (int k = 0; k < 10000; k++) {}}}long end = System.currentTimeMillis();System.out.println("Time spend is " + (end - start) + "ms");start = System.currentTimeMillis();for (int i = 0; i < 10000; i++) {for (int j = 0; j < 1000; ++j) {for (int k = 0; k < 100; k++) {}}}end = System.currentTimeMillis();System.out.println("Time spend is " + (end - start) + "ms");}}

相同的总的循环次数,但是内外层循环次数不同,运行结果如下,可见外层多内层少的循环更加耗时

原因分析:

假设使用静态分支预测,两种循环预测错误的次数如下图所示,

可见预测错误次数越多,清除流水线的次数就越多,性能就越差

5. SuperscalarVLIW

5.1 多发射与超标量

1. 在多发射(Multiple Issue)和超标量(Superscalar)技术中,同一时间会取出多条指令发射到不同的译码器及后续的流水线中

2. 在超标量CPU中,有多条并行的流水线

3. ALU的不同FU(功能单元)可以并行工作,构成不同的流水线,且不同功能单元的流水线级数是不同的

说明:在讲CPU的硬件组成时,将所有算数和逻辑运算都抽象为ALU,但是在硬件电路实现层面,加法器、乘法器、浮点计算部分等都是分开实现的

5.2 VLIW技术

1. 上节说明的是动态多发射处理器,需要在CPU硬件中处理依赖关系

在引入多发射超标量之后,依赖关系的处理更加复杂

2. VLIW(Very Long Instruction Word,超长指令字)设计的思路,是让编译器来在编译阶段处理指令的依赖关系

3. VLIW中处理指令的依赖关系后,将没有依赖关系的指令打包成一个指令包,之后CPU直接取出指令包并译码执行

说明:VLIW失败的原因

① 不能向前兼容

之前在X86上的程序不能在VLIW架构上运行

② 不能向后兼容

VLIW架构的并行度是由指令包中包含的指令个数决定的,如果从3个提升到6个,需要更新编译器,并重新编译所有程序

之所以需要更新编译器,是因为原来编译器判断的依赖关系是在3个指令以及由3个指令组成的指令包中;现在变成6个指令和6个指令组成的指令包

6. SIMD

6.1 超线程技术

6.1.1 引入背景

由于Pentium 4处理器流水线级数太深,流水线中的指令就多,相互依赖关系就多,导致流水线不得不停顿

6.1.2 超线程实现线程级并行

1. 之前所有解决冒险的技术,都是为了实现指令级并行,即让CPU在同一时间,去并行地执行多条命令,而这些命令之间可能是有依赖关系的

而这些依赖关系,不可避免地会导致流水线停顿

2. 超线程(Hyper-Threading)技术的思路是,当流水线停顿时,去运行一些没有依赖关系的指令。而不同程序中的指令,天然就是没有依赖的

3. 在硬件实现层面,超线程CPU将一个物理层面的CPU核心"伪装"成两个逻辑层面的CPU核心,使得我们可以在一个CPU核心内部,维护两个不同线程的指令状态信息

当线程1的指令在流水线停顿时,计算资源就处于空闲状态,此时就让线程2的指令去执行,而线程2的指令与线程1的指令是没有依赖关系的

说明1:超线程技术也被称作同时多线程(Simultaneous Multi-Threading,SMT)技术

说明2:指令译码器和ALU每个CPU核心只有一份,由于并没有增加真正的功能单元,所以超线程只在特定的应用场景下效果较好,一般是各个线程等待时间比较长的应用场景

说明3:理清超线程的概念

多个CPU核心运行不同的程序,或者单个CPU核心切换运行不同的程序,在同一时间点上,一个物理CPU核心只会运行一个线程的指令,所以并不是线程级的并行

而超线程概念,则是在一个物理CPU中同时运行多个线程的指令,实现线程级并行

6.2 SIMD技术

1. SIMD(Sigle Instruction Multiple Data),单指令多数据

2. SIMD在获取数据和执行指令时,都做到了并行

实例:在支持SIMD的CPU上进行int型向量加法运算

1. Intel在引入SSE指令集时,向CPU中添加了8个16B的寄存器,即一次可以加载4个int型整数

2. 在进行int型向量加法运算时,SIMD指令一次可以从内存中读取4个整数,相较于循环读取4次,提升了性能

3. 在指令执行层面,SIMD也可以并行进行。4个整数的相加相互之间是没有依赖的,也就没有冒险的问题需要处理

所以SIME适用于存在大量数据并行的计算中,也就是实践中的向量运算或矩阵运算

7. 异常和中断

7.1 软硬件组合处理

7.1.1 概述

1. 异常是一个软硬件组合处理的过程

2. 异常的发生和捕捉由硬件完成

3. 异常的处理由软件完成

7.1.2 异常处理流程

1. 计算机为每种可能的异常分配一个异常代码(Exception Number),也称作中断向量(Interrupt Vector),本质上就是一个编号

2. 计算机在内存中还会保留一个异常表(Exception Table),也称作中断向量表(Exception Vector Table),而这个表中存放的是异常代码对应的异常处理程序(Exception Handler)所在的地址

3. 当异常发生时,会先把当前的程序执行线程保存到栈中,然后根据异常码,在异常表中找到对应的异常处理程序,并将控制权交给异常处理程序

7.2 异常的分类

说明1:中断(interrupt)、陷阱(trap)、故障(fault)、中止(abort)

说明2:故障和陷阱的区别在于,陷阱是在开发程序时故意触发的,而故障通常不是

说明3:故障和中断、陷阱的一个重要区别,是故障在异常处理完成之后,仍然回来处理当前的指令,而不是去执行下一条指令,因为当前的指令因为故障的原因并没有成功执行完成

说明4:中止可以理解为故障的一种特殊情况,当CPU遇到故障,但是无法恢复时,程序就不得不中止

7.3 异常处理与函数调用的区别

1. 因为异常情况往往发生在预期之外,所以除了本来函数调用压栈要完成的工作,还需要将CPU当前运行程序用到的其他所有寄存器均压栈

2. 异常处理涉及用户态和内核态的切换

8. CISCRISC

8.1 特征概述

说明1:CISC设计思路

① 为了性能考虑,很多功能直接通过硬件电路完成。但是同时会导致电路复杂,而电路复杂又会导致功耗增加

② 为了少用内存,指令长度可变,常用的指令短一些,不常用的指令可以长一些。但是同时会导致编译器优化困难

CISC的设计思路体现了当时CPU硬件能力与资源限制的影响

说明2:RISC设计思路

① 在实际程序中,80%的时间都在使用20%的简单指令

② 将指令集精简到20%的简单指令,原先的复杂指令,则通过简单指令组合来实现,让软件来实现硬件的功能

说明3:RISC CPU需要更多通用寄存器

① 因为RISC完成同样的任务,执行的指令数比CISC多,需要反复从内存中读取指令或数据到寄存器中,所以访问内存耗时较多

因此RISC架构的CPU往往会设置更多的通用寄存器

② 由于RISC CPU中完成指令的电路变得简单,便空出了更多的空间,该空间常被用来存放更多的通用寄存器

说明4:不同的优化路径

程序的CPU运行时间 = 指令数 * CPI * Clock Cycle Time

① CISC架构通过优化指令数来减少执行时间

② RISC架构通过优化CPI来减少执行时间,因为简单指令需要的时钟周期比较少

8.2 微指令架构

8.2.1 Intel使用CISC架构的原因

1. Intel出于兼容性的考虑,无法抛弃原有架构,重新开发一款RISC CPU

2. x86下的64位指令集x86-64并不是Intel发明的,而是AMD发明的。因此在Ubuntu下通过apt安装程序时,随处可见AMD64的关键字

8.2.2 Intel引入微指令架构

1. 考虑到RISC架构的优点,Intel引入微指令架构的目的,就是让CISC风格的指令集,以RISC的形式在CPU中运行。而微指令架构的引入,也让CISC和RISC的分界变得模糊

2. 在指令译码阶段,微指令架构的译码器会将一条机器码翻译为好几条微指令,这些微指令是RISC风格的,均为固定长度

3. RISC风格的微指令会被放到一个微指令缓冲区中,然后再从缓冲区分发给后面的超标量,并且是乱序执行的流水线架构中

说明1:微指令架构中译码器的角色

微指令架构中的译码器类似设计模式中的适配器(Adaptor),弥合了CISC和RISC指令之间的差异

说明2:微指令架构性能

在微指令架构中,译码器比原来复杂,这也就意味着更长的译码时间,这点与引入RISC提升性能是背道而驰的

考虑到RISC指令集的设计思路就是指令的局部性原理,所以Intel在CPU中增加了L0 Cache,这个Cache保存的就是指令译码器将CISC指令翻译成RISC微指令的结果。在大部分情况下,CPU都可以直接从L0 Cache中得到译码结果,而无需让译码器实际译码,进而提升了性能

9. 理解虚拟机

9.1 概述

1. 虚拟机技术的出现是为了达到"零售"服务器计算资源的目的

2. 虚拟机技术是指在现有硬件的操作系统上,能够模拟一个计算机系统

9.2 解释型虚拟机

1. 解释型虚拟机本质上是一个应用程序,运行在已有硬件的操作系统中。该应用程序可以识别我们想要模拟的计算机系统的程序格式和指令集,然后对指令逐条解释执行

2. 在这个过程中,原先的操作系统叫做宿主机(Host);将有能力去模拟指令执行的软件叫做模拟器(Emulator);而实际运行在模拟器上的被虚拟出来的系统叫做客户机(Guest VM)

3. 解释型虚拟机最大的优点是可以跨硬件,但与此同时也带来2个缺点,

① 不能做到精确模拟,比如有些老旧硬件的程序运行,要依赖特定电路乃至电路特有的时钟频率

② 由于不是将指令交给CPU执行,而是需要经过各种解释和翻译工作,所以解释型虚拟机性能很差

说明1:解释型虚拟机是一种进程级虚拟机,比如JVM,qemu,当然JVM不是模拟特定的计算机系统指令,而是Java自定义的中间代码(字节码)

说明2:解释型虚拟机的性能优化

类似Java中JIT的方法,将本来解释执行的指令编译成Host可以直接运行的指令

9.3 全虚拟化虚拟机

9.3.1 概述

1. 全虚拟化技术可以在现有的物理服务器硬件和操作系统之上,运行一个完整的、不需要做任何修改的客户机操作系统(Guest OS)

2. 全虚拟化技术可以克服解释型虚拟机不能精确模拟与性能差的缺点,但同时也必须放弃解释型虚拟机可以跨硬件平台的能力

9.3.2 Hypervisor的引入

1. 实现全虚拟化技术的方式就是加入一个中间层,即虚拟机监视器VMM(Virtual Machine Manager)或Hypervisor

2. 运行的虚拟机与Hypervisor交互,会把整个硬件特征都映射到虚拟机环境中,包括整个完整的CPU指令集、IO操作、中断等

9.3.3 Type-1 & Type-2型虚拟机

Type-1 & Type-2型虚拟机的主要差别在于Hypervisor运行的位置,Type-2型在用户态,Type-1型在内核态

9.3.3.1 Type-2型虚拟机

1. Hypervisor是一个运行在操作系统之上的应用程序

2. 客户机操作系统将最终发送到硬件的指令都发送给Hypervisor,Hypervisor再将这些指令交给宿主机操作系统去执行

3. 从工作原理上,Type-2型虚拟机与解释型虚拟机类似,所以更多用于日常个人PC中,而不是用于数据中心

9.3.3.2 Type-1型虚拟机

1. Hypervisor不是一个运行在操作系统之上的应用程序,而是嵌入在操作系统内核中,e.g. KVM,XEN

2. 客户机的指令交给Hypervisor之后,不再需要通过宿主机的操作系统才能调用硬件,而是可以直接由Hypervisor去调用硬件

3. 由于在数据中心中,不需要在X86上运行一个ARM程序,而是直接在X86上虚拟一个X86硬件的计算机和操作系统,所以无需翻译工作,直接传递执行即可,效率也更高

9.4 操作系统级虚拟机

9.4.1 Type-1型虚拟机的缺点

1. Type-1型虚拟机已经没有什么硬件损耗,但是存在一个浪费的资源,就是每个虚拟机都运行了一个属于自己的单独的操作系统

2. 多运行一个操作系统就意味着多消耗一些CPU、内存等资源

3. 很多情况下,我们未必想要一个完整的、独立的、全虚拟化的虚拟机,而是独立的计算资源,所以只要一个能够进行资源和环境隔离的独立空间就能满足需求

9.4.2 基于Docker的容器技术

1. 在实际的服务器端开发中,虽然应用环境需要各种不同的依赖(e.g. 不同的库文件),但是通常来说,都是运行在Linux内核之上。所以通过Docker就不需要在操作系统上再运行一个操作系统

2. Docker并不能算是一种虚拟机技术,而是一种资源隔离技术

深入浅出计算机组成原理03:处理器相关推荐

  1. 极客时间 自我提升第二天 数据结构与算法之美 应该掌握 / 趣谈网络原理 / 深入浅出计算机组成原理 思维导图

    菜鸟今天又来完成所说的诺言,也希望大家督促,在今天的学习中,菜鸟有了新的认知,我会将上一篇中理解不完善的一些地方进行补充,学习本就是不断打破自己的认知,如果思考都不做,何来的知识的积累 文章目录 数据 ...

  2. 【计组】入门篇 --《深入浅出计算机组成原理》(一)

    课程链接:深入浅出计算机组成原理_组成原理_计算机基础-极客时间 目录 一.为什么需要学习计算机组成原理 二.冯·诺依曼体系结构:计算机组成的金字塔 1.计算机的基本硬件组成 2.冯·诺依曼体系结构 ...

  3. 【练拳不练功,到老一场空】深入浅出计算机组成原理

    深入浅出计算机组成原理 文章目录 深入浅出计算机组成原理 计算机的基本组成 硬件设备组成 CPU 内存 主板 I/O 设备 硬盘 显卡 冯.诺依曼体系结构 运算器/处理器单元 控制器 存储器 输入设备 ...

  4. 深入浅出计算机组成原理 通过CPU主频看性能(自我提升第8天)

    希望大家关注菜鸟,不然后期的文章,各位可能无法及时看到 文章目录 深入浅出计算机组成原理 1.计算机性能的两个指标 2.计算机的计时单位: CPU时钟 大家了解了上面的知识点,那接下来就是两者结合的高 ...

  5. 【计组】设计大型DMP系统--《深入浅出计算机组成原理》(十四)

    目录 一.DMP:数据管理平台 二.MongoDB 真的万能吗 三.关系型数据库:不得不做的随机读写 (一)Cassandra:顺序写和随机读 1.Cassandra 的数据模型 2.Cassandr ...

  6. 深入浅出计算机组成原理 指令跳转(自我提升第十八天)

    上节学这个徐文浩的深入浅出计算机组成原理,就吃了大亏,渐渐的就发现了,其实他讲的并不单纯是计算机组成原理,而是讲的汇编语言和单片机,(lll¬ω¬)汗! 菜鸟倒是还好,因为学过单片机的,汇编语言多少了 ...

  7. 深入浅出计算机组成原理01:计算机概要与技术

    目录 1. 计算机系统结构中的8个伟大思想 1.1 面向摩尔定律设计 1.2 使用抽象简化设计 1.3 加速大概率时间 1.4 通过并行提高性能 1.5 通过流水线提高性能 1.6 通过预测提高性能 ...

  8. 深入浅出计算机组成原理(四)——穿越功耗墙,我们该从哪些方面提升“性能”?

    文章目录 功耗:CPU 的"人体极限" 并行优化,理解阿姆达尔定律 总结延伸 补充阅读 课后思考 上一讲,在讲 CPU 的性能时,我们提到了这样一个公式: 程序的 CPU 执行时间 ...

  9. 深入浅出计算机组成原理04-穿越功耗墙,我们该从哪些方面提升“性能”?

    上一讲,在讲 CPU 的性能时,我们提到了这样一个公式: 程序的 CPU 执行时间 = 指令数×CPI×Clock Cycle Time 这么来看,如果要提升计算机的性能,我们可以从指令数.CPI 以 ...

最新文章

  1. 批量替换文本中字符代码-python3
  2. 【集合论】卡氏积 ( 卡氏积概念 | 卡氏积示例 | 卡氏积性质 | 非交换性 | 非结合性 | 分配律 | 有序对为空 | n 维卡氏积 | n 维卡氏积个数 | n维卡氏积性质 )
  3. Android之Xposed框架完全使用指南
  4. tensorflow 开始——创建定制化 Estimator(创建自定义评估器)
  5. 异常处理机制——panic 和 recover
  6. 异步广度优先搜索算法
  7. python的遍历字典里的键然后放到一个列表里_Python列表和字典互相嵌套怎么办?看完让你没有疑惑...
  8. 【白皮书分享】2020双十一五大趋势洞察白皮书.pdf(附下载链接)
  9. Enterprise Library Step By Step系列(十六):使用AppSetting Application Block
  10. 初学者易上手的SSH-hibernate01环境搭建
  11. 入门:Mac终端常用知识
  12. Linux搭建邮件服务器postfix
  13. Tomcat8如何配置项目appBase和docBase
  14. jLBJwOvvyU
  15. PCF8563实时时钟模块功能实现
  16. 程序报错误Illegal instruction的解决办法
  17. 分治法一个整数数列求最大值最小值_五大常见算法策略之丨递归与分治策略
  18. 智慧警务:如何利用视频智能分析技术助力城市警务可视化综合监管与指挥系统
  19. Spark Streaming + ES构建美团App异常监控平台
  20. C3P0的三种使用方法

热门文章

  1. php完全安装安装,服务器_Apache Web 服务器的完全安装指南,所需软件apache_ - phpStudy...
  2. 计算机网络通信选择题,计算机网络教(学)案通信技术选择题试题题库完整
  3. 九阴真经 服务器列表文件,《九阴真经》部分服务器互通升级公告
  4. 脉位调制解调 matlab,基于matlab的am调制解调
  5. mysql数据库下的所有表字段
  6. Oracle触发器4-数据库事件触发器
  7. vue ---- 工程化概念、webpack概念、webpack的安装配置,以及简单使用
  8. MYSQL round()函数
  9. centos7.8离线安装gcc
  10. Mybatis注解的方式,如何实现MySQL ,update后,返回更新后的值