计算机组成 -- MESI协议
缓存一致性问题
iPhone降价了,要把iPhone最新的价格更新到主内存里,为了性能问题,采用写回策略
先把数据写入到L2 Cache里,然后把Cache Block标记为脏的
此时数据其实没有被同步到L3 Cache或主内存里
1号核心希望在这个Cache Block要被交换出去的时候,数据才写入到主内存里
此时2号核心尝试从内存里读取iPhone的价格,就会读取一个错误的价格
缓存一致性问题:1号核心和2号核心的缓存,此时是不一致的
同步机制能够达到的目标
写传播(Write Propagation)
在一个CPU核心里面的Cache数据更新,必须能够传播到其他对应节点的Cache Line里
事务串行化(Transaction Serialization)
在一个CPU核心里面的读取和写入,在其他节点看起来,顺序是一样的
事务串行化
1号核心先把iPhone的价格改成5000,差不多时间,2号核心把iPhone的价格改成6000,这两个修改会传播到3号核心和4号核心
3号核心先收到2号核心的写传播,再收到1号核心的写传播;4号核心刚好相反
虽然写传播做到了,但各个 ...
计算机组成 -- 高速缓存
缓存行1234567$ sysctl -a | grep -E 'cacheline|cachesize'hw.cachesize: 17179869184 32768 262144 6291456 0 0 0 0 0 0hw.cachelinesize: 64 # 64 Byteshw.l1icachesize: 32768 # 32 KBhw.l1dcachesize: 32768 # 32 KBhw.l2cachesize: 262144 # 256 KBhw.l3cachesize: 6291456 # 6 MB
1234567891011121314151617181920212223242526public static void f1() { int[] arr = new int[64 * 1024 * 1024]; long start = System.currentTimeMillis(); for (int i = 0; i < arr.length; i++) { arr[i] *= 3; ...
计算机组成 -- 局部性原理
局部性原理
局部性原理:时间局部性(temporal locality)+空间局部性(spatial locality)
时间局部性:如果一个数据被访问了,在短时间内还会被再次访问
空间局部性:如果一个数据被访问了,和它相邻的数据也很快会被访问
亚马逊的商品假设:总共6亿商品,每件商品需要4MB的存储空间,总共需要2400TB的数据存储
存储成本
如果所有数据都放在内存里面,需要3600万美元
$3600 = \frac{2400TB}{1MB}×0.015$
如果只在内存里存放前1%的热门商品,其余的放在HDD上,存储成本可以下降为45.6万美元,即原来成本的1.3%
$45.5 ≈ 3600 \times 0.01+3600 \times 0.99 \times \frac{0.00004}{0.015}$
时间局部性
时间局部性:LRU(Least Recently Used)缓存算法
热门商品被访问得多,就会始终被保存在内存里,而冷门商品被访问得少,就只存放在HDD硬盘上
越是热门的商品,越容易在内存中找到,能更好地利用内存的随机访问性能
假设日活为1亿,活跃用户每人每天 ...
计算机组成 -- 存储层次结构
SRAM
CPU类比成计算机的大脑;而正在思考的东西,可以类比成CPU中的寄存器(Register)
寄存器更像是CPU本身的一部分,存放极其有限的信息,但速度非常快,和CPU同步
大脑中的记忆,类比成CPU Cache(高速缓存)
CPU Cache使用的芯片是SRAM(Static Random-Access Memory,静态随机存取存储器)
静态:只要处于通电状态,里面的数据就能保持存在,而一旦断电,里面的数据就会丢失
在SRAM里,1个比特的数据,需要6~8个晶体管
所以SRAM的存储密度不高,同样的物理空间下,能够存储的数据有限
SRAM的电路简单,所以访问速度非常快
在CPU里,通常会有L1、L2、L3这三层高速缓存
L1 Cache
每个CPU核心都有一块独占的L1 Cache,通常分为指令缓存和数据缓存
L1 Cache通常嵌在CPU核心的内部
L2 Cache
L2 Cache同样是每个CPU核心都有,但往往不在CPU核心的内部,因此L2 Cache的访问速度会比L1 Cache稍慢
L3 Cache
L3 Cache通常是多个CPU核心共用的,尺寸更大,访问速度更慢
...
计算机组成 -- 虚拟机
解释型虚拟机
要模拟一个计算机系统,最简单的办法,就是兼容这个计算机系统的指令集
开发一个应用程序,运行在操作系统上,该应用程序可以识别想要模拟的计算机系统的程序格式和指令,然后一条条去解释执行
原先的操作系统称为宿主机(Host),有能力模拟指令执行的软件称为模拟器(Emulator)
实际运行在模拟器上被虚拟出来的系统,称为客户机(Guest VM)
这种方式和运行Java程序的JVM比较类似,只不过JVM运行的是Java中间代码(字节码),而不是一个特定的计算机系统的指令
真实的应用案例:Android模拟器、游戏模拟器
优势
模拟的系统可以跨硬件
Android用的是ARM CPU,开发机用的是Intel X86 CPU,两边的CPU指令集是不一样的,但一样可以正常运行
劣势
无法做到精确模拟
很多老旧的硬件的程序运行,需要依赖特定的电路乃至电路特有的时钟频率,很难通过软件做到100%模拟
性能很差
并不是直接把指令交给CPU去执行,而是要经过各种解释和翻译的工作
编译优化:Java的JIT
把本来解释执行的指令,编译成Host可以直接运行的指令
全虚拟化
全虚拟化 ...
计算机组成 -- CISC + RISC
历史
在早期,所有的CPU都是CISC
实际的计算机设计和制造会严格受到硬件层面的限制,当时的计算很慢,存储空间很小
为了让计算机能够尽量多地工作,每个字节乃至每个比特都特别重要
CPU指令集的设计,需要仔细考虑硬件限制,为了性能考虑,很多功能都直接通过硬件电路来完成
为了少用内存,指令长度也是可变的
常用的指令要短一些,不常用的指令要长一些
用尽量少的内存空间,存储尽量多的指令
计算机的性能越来越好,存储的空间也越来越大,70年代末,RISC出现
CPU运行程序,80%的运行代码都在使用20%的简单指令
对比
CISC
RISC
以硬件为中心的指令集设计
以软件为中心的指令集设计
通过硬件实现各类程序指令
通过编译器实现简单指令组合,完成复杂功能
更高效地使用内存和寄存器 – 一开始都是CISC,硬件资源非常珍贵
需要更大的内存和寄存器,并更频繁地使用
可变的指令集,支持更复杂的指令长度
简单、定长的指令
大量指令数
少量指令数
CISC的缺点
在硬件层面,如果想要支持更多的复杂指令,CPU里面的电路就要更复杂,设计起来更困难
更复杂的电路,在散热和功耗层面 ...
计算机组成 -- 异常
异常
异常是一个硬件和软件组合在一起的处理过程
异常的发生和捕捉,是在硬件层面完成的
异常的处理,是在软件层面完成的
计算机会为每一种可能发生的异常,分配一个异常代码(Exception Number),别称中断向量(Interrupt Vector)
异常发生的时候,通常是CPU检测到一个特殊的信号
在组成原理里面,一般叫作发生了一个事件(Event),CPU在检测到事件的时候,就已经拿到了对应的异常代码
异常代码
IO发出的信号的异常代码,是由操作系统来分配,即由软件来设定
像加法溢出这样的异常代码,是由CPU预分配的,即由硬件来设定
拿到异常代码后,CPU会触发异常处理流程
计算机在内存里,会保留一个异常表(Exception Table),别称中断向量表(Interrupt Vector Table)
存放的是不同的异常代码对应的异常处理程序所在的地址
CPU拿到异常代码后,会先把当前程序的执行现场(CPU当前运行程序用到的所有寄存器),保存到程序栈里面
然后根据异常代码查询,找到对应的异常处理程序,最后把后续指令执行的指挥权,交给这个异常处理程序
异常可以由硬件触发,也可以由软件触发 ...
计算机组成 -- 超线程 + SIMD
超线程 – 线程级并行Pentium 4
Pentium 4失败的原因:CPU的流水线级数太深
超长的流水线,使得之前很多解决冒险、提升并发的方案都用不上
解决冒险、提升并发的方案,本质上是一种指令级并行的技术方案,即CPU希望在同一个时间,去并行执行两条指令
但这两条指令,原本在代码里是有先后顺序的
无论是流水线架构、分支预测以及乱序执行,还是超标量和超长指令字
都是想通过在同一时间执行两条指令,来提升CPU的吞吐率
但在Pentium 4上,上面这些方法都可能因为流水线太深,而起不到效果
更深的流水线意味着同时在流水线里面的指令就很多,相互的依赖关系就多
因此,很多时候不得不把流水线停顿下来,插入很多NOP操作,来解决这些依赖带来的冒险问题
超线程
无论是多个CPU核心运行不同的程序,还是单个CPU核心里切换运行不同线程的任务
在同一时间点上,一个物理的CPU核心只会运行一个线程的指令,其实并没有做到真正的指令级并行
超线程的CPU,把一个物理层面的CPU核心,伪装成两个逻辑层面的CPU核心
这个CPU会在硬件层面增加很多电路,使得可以在一个CPU核心内部,维护两个不同线程的指令的状 ...
计算机组成 -- Superscalar + VLIW
吞吐率
程序的CPU执行时间 = 指令数 × CPI × Clock Cycle Time
CPI = Clock Per Instruction
IPC = 1/CPI = Instruction Per Clock
一个时钟周期内能够执行的指令数,代表了CPU的吞吐率
最佳情况下,IPC只能到1
无论做了哪些流水线层面的优化,即使做到了指令执行层面的乱序执行
CPU仍然只能在一个时钟周期内取一条指令!!
无论指令后续无论优化得多好,一个时钟周期也只能执行一条指令,IPC只能是1
但Intel CPU或者ARM CPU,一般IPC能做到2以上
多发射 + 超标量
整数计算过程和浮点数的计算过程差异比较大
整数计算和浮点数计算的电路,在CPU层面是分开的
一直到80386,CPU都是没有专门的浮点数计算的电路的,当时的浮点数计算,都是通过软件进行模拟的
在80386时代,Intel给386配了单独的387芯片,专门用来做浮点数运算
386dx:带387浮点数计算芯片
386sx:不带387浮点数计算芯片
CPU会有多个ALU,在指令执 ...
计算机组成 -- 冒险
冒险
流水线架构的CPU,是主动进行的冒险选择,期望通过冒险带来更高的回报
对于各种冒险可能造成的问题,都准备好了应对方案
分类
结构冒险(Structural Hazard)
数据冒险(Data Hazard)
控制冒险(Control Hazard)
结构冒险
结构冒险,本质上是一个硬件层面的资源竞争问题
CPU在同一个时钟周期,同时在运行两条计算机指令的不同阶段,但这两个不同的阶段可能会用到同样的硬件电路
内存的数据访问
第1条指令执行到访存(MEM)阶段的时候,流水线的第4条指令,在执行取指令(Fetch)操作
访存和取指令,都是要进行内存数据的读取,而内存只有一个地址译码器,只能在一个时钟周期内读取一条数据
无法同时执行第1条指令的读取内存数据和第4条指令的读取指令代码
解决方案
解决方案:增加资源
哈佛架构
把内存分成两部分,它们有各自的地址译码器,这两部分分别是存放指令的程序内存和存放数据的数据内存
缺点:无法根据实际情况去动态调整
普林斯顿架构 – 冯.诺依曼体系架构
今天使用的CPU,仍然是冯.诺依曼体系架构,并没有把内存拆成程序内存和数据内存两部分
混合架 ...