《小学生也看得懂的红石计算机教程:从零构建自己的红石计算机》













楼主 savenseg  发布于 2019-03-18 13:47:00 +0800 CST  
有。。。人吗

楼主 savenseg  发布于 2019-03-18 15:53:00 +0800 CST  
===============================================================
【###======
【#CHAPTER[2]-2.1【缓冲器】
【###======
从CHAPTER[2]开始,这个教程会默认玩家对红石数字电路已经熟悉,
这么做并不是没有理由:首先,大部分数电玩家都早已熟悉数字电路,
其次,网络上已经有了非常丰富的红石数字电路教学资源,所以我就
没有必要再重复复述别人已经讲过的内容.


那么进入正题,这一节我们要理解缓冲器(buffer)的思想.
buffer是什么?顾名思义就是用来缓冲数据的地方,
在CHAPTER[1]中,每个接口就充当了一个缓冲器的作用,
在收到数据时,临时保存缓冲数据.
所以说,buffer就是这样一个用来缓冲数据的东西.
它可以缓冲很多东西,比如说指令:
我们知道RAM传送一次数据到CPU是一个很慢的过程,
所以我们要减少RAM与CPU之间的通讯次数,
这时候buffer就出场了.我们一次性从RAM里连续提取多条指令,
保存在CPU的指令队列器中,就不需要在每执行完一个指令后又
去向RAM提取下一个指令,这时候我们只需要从指令队列器里
提取出事先提取出来的指令就可以了,假设指令队列器可以缓冲n
条指令,那么我们可以每隔n个指令执行周期才进行一次访存(访问内存),
如果CPU在执行指令的时候可以顺便访存提取指令,那么可以在指令
队列器变空之前就再次提取指令将它填满.
但是很可惜这招对分支指令没啥用:
因为分支指令如果条件成立后,会跳转到一个新的地址开始执行指令,
所以之前提取的指令都作废了,需要重新提取指令.
当然分支预测可以应对这个问题,但不是总有效
===============================================================

楼主 savenseg  发布于 2019-03-18 23:32:00 +0800 CST  
===============================================================
【###======
【#CHAPTER[2]-2.2【寻址模式与内存管理】
【###======
我们来思考一个问题:
如果一个CPU只有8bits的宽度,该如何解决地址空间不足的问题?
一个很简单而且高效并且还有附加好处的办法就是:


分段寻址,比如说我们可以把R7作为程序计数器PC,而R8作为段寄存器,
然后CPU的地址总线的输出就是R7,R8两个寄存器里的值的粘合.
比如说R7=10101100,R8=11111111,
那么CPU提取指令时给地址接口放的地址是:


11111111,10101100,高8位数段寄存器内容,低8位是PC的内容,
这样我们就拥有了一个16bits的寻址范围,但是注意到,
PC+1只是针对PC的,如果PC=11111111的时候再+1,会直接导致
PC溢出,因为它又不是跟段寄存器连起来的.
我们添加段寄存器导致的结果是这样的:


内存空间被段寄存器划分成了11111111+1=255段,
同理每段都有255个单元,
寻址的时候,首先会根据段寄存器内容选择一个段,
然后再根据PC在这个段内部选择单元.
这样虽然我们可以拥有16bits的地址空间,但是每个段都被限制为255
个单元,如果代码用的内存单元数量超过了255,就需要用跳转跳到
下一段,否则会直接溢出.
(这里的JMP必须能同时修改两个寄存器的值)


这么做的结果是强制程序员改变编程的习惯:
程序员得把一个程序分解成几个段,然后实际执行时,
如果要执行非当前段内的代码,就必须在段间进行跳转.


这么做也不是完全都是坏的:
划分为255段后,我们可以让某些特殊段具有特殊功能,
比如让段0到段7存储者一个简单的操作系统,
然后IO设备的输出映射到了段8,IO设备的输入被映射到了段9,
我们可以要求所有代码不能在段8内写数据,不能在段9内读数据,
让除了操作系统段里的代码,其它段的代码都不能访问0到7段里的单元.
这就是一个简陋的安全模型,
CPU可以通过判断段寄存器的值来确定当前执行的指令的权限级别.
===============================================================

楼主 savenseg  发布于 2019-03-18 23:33:00 +0800 CST  
===============================================================
【###======
【#CHAPTER[2]-2.3【堆栈指针与连续调用】
【###======
在CHAPTER[1]中的指令CALL有缺陷,
那就是调用程序里不能存在CALL,
因为如果有CALL指令,那么调用程序它会把自己当前的地址保存到R30里
(R30是之前规定用来暂存调用时地址的寄存器),覆盖了第一次调用
的地址,这样程序就跳转不回去了,永远卡在了调用程序里.


为了解决这个办法,我们引入了堆栈,
每次执行CALL的时候,直接将当前地址压入栈内,
然后在执行RET的时候再把栈顶的元素弹出,
这样如果在调用程序里出现了CALL,即使连环套了很多个CALL,
也会随着RET的顺序执行逐渐返回到初始的地方:
CALL->CALL->CALL->CALL->RET->RET->RET->RET.
当然堆栈也有溢出的风险,所以如果这种俄罗斯套娃套的太多,
还是会出问题的.


那么我们该怎么给CPU添加堆栈呢?
因为调用深度(就是套了多少次娃的意思,套越深)可能会很大,
CPU内部提供这么一个大长条的堆栈硬件,多少有点不方便.
所以就出现了堆栈指针寄存器IP这种东西:


IP里面内容指向的内存单元就是堆栈的栈顶,
CPU会提供POP(出栈),PUSH(入栈)两个指令,
执行PUSH的时候,会先让SP+1
然后把指定寄存器里的值复制到SP指向的内存单元,
执行POP时候,会先把SP指向内存单元中的值复制到指定寄存器内,
然后再让SP-1


这样就实现了堆栈的效果.但是我们之前提到,像8bitsCPU这样的CPU,
一般得采用分段寻址的方法拓展地址空间,这样内存分段了,该怎样让
堆栈指针也跟上来呢?,就是再调一个寄存器改名CS,
这个寄存器里的内容就是指向栈底部的位置,
然后执行POP和PUSH的时候,实际指向的单元是CS:IP,也就是CS和IP
粘起来的16bits,就像之前的分段寻址一样.
===============================================================

楼主 savenseg  发布于 2019-03-18 23:34:00 +0800 CST  
===============================================================
【###======
【#CHAPTER[2]-2.4【总线结构】
【###======
有没有被CHAPTER[1]里面接口的花哨命名和接口间混乱的数据转移
弄晕呀?其实是我之前没告诉你方法.


实际设计CPU的时候,为了防止弄晕,也为了让模块间的连接统一,
会引入一种叫做总线的结构,
总线就是一根线,然后所有的模块的数据输入输出全部都被
挂到上面,这样要把某个接口的数据发到另一个接口的数据的话,
就可以把步骤简化成这样:
把需要发送和接受数据的接口与总线相连接(可以用与门实现),
这样数据就能成功转移了.但是注意到,
这个总线被一对接口同时占有,这样比如我们需要
做A <- B,C <- D两个数据的转移,
如果A,B;C,D之间有独立的连接,它就能立刻完成,
而如果是挂在总线上面,它就得分两次进行.


在CPU里,我们通常会挂多个总线,
比如说有A,B,C,D,E,F,G,H这八大模块之间转发数据,
而A,B,C之间的数据转发尤为频繁,
那么我们可以让
A,B,C,D,E,F,G,H共享一个总线,
然后让A,B,C又独享一个总线,得到了独立的带宽.
就部分优化了这个问题,


总线上有多个设备同时请求使用总线的时候,
我们需要判断接受这些请求的顺序,
这就要用到所谓的总线仲裁单元,
可以是按顺序论询:


A->B->C...->H->A这样轮流询问是否有请求,
也可以优先执行A的请求,A没请求就执行B的,B没就执行C....这样,
当然这么做会导致H排在最后没什么机会,就很不公平,
所以设计总线仲裁机制的时候,我们会说让所有设备间
使用总线的机会总是差不多,也就是对他们平等对待,
但是如果A的请求很重要很急的话,可以提高A的优先级.
优先执行A的请求.


仲裁逻辑有很多,也可以百度.
在CPU外面也有总线,总线上挂着很多IO设备,当然CPU和RAM之前肯定
是有独立的连接了,但是IO设备通过总线和CPU和RAM连接.
并且遵循仲裁机制获得表现的机会
===============================================================

楼主 savenseg  发布于 2019-03-18 23:35:00 +0800 CST  
===============================================================
【###======
【#CHAPTER[2]-2.5【中断系统】
【###======
中断是用来处理某些紧急事故的系统,
中断触发指令是INT,但是有时候不使用这种指令也会有触发中断
的情况,比如某个CPU会检测加法运算的溢出,溢出就触发中断.
中断触发后会立即执行中断程序,
但是有个问题:如果要立即执行中断程序,当前寄存器里的值怎么办?
事实上完全不止这么简单,有时候ALU内部的值都要保存,什么都要保存,
全部保存.保存办法之一就是把这些数据压入栈,中断程序结束后,
如果这个中断程序尾部有RET(有的中断程序执行完就关机了,也就不需要
恢复现场),
那就得把保存在栈里的数据一个一个排出来,塞回原来的地方.
中断程序是这样的:
内存里存在一个“中断向量表”,
有n个项,每个项对应的内容就是这个项对应的中断程序的起始地址,
所以INT n会执行第n项中断程序,这个n就叫中断向量.
中断向量表里大部分项对应的地址是已经设置好的,
还有一些会提供给操作系统设置,
还有一些会提供给程序设置,
那些提供给操作系统或者程序设置的中断向量号对应的地址处
空空如也,等你在那里写东西.
===============================================================

楼主 savenseg  发布于 2019-03-18 23:36:00 +0800 CST  
===============================================================
【###======
【#CHAPTER[2]-2.6【流水化方法】
【###======
这节我们讲看起来很吊其实还是很捞的东西:流水线.(不是富士康)
流水线就是让多条指令重叠起来执行.
有张很简单的图:
【图】
假设我们整一个所谓的多周期CPU:


$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$


多周期CPU指的是将整个CPU的执行过程分成几个阶段,
每个阶段用一个时钟去完成,然后开始下一条指令的执行,
而每种指令执行时所用的时钟数不尽相同,这就是所谓的多周期CPU。
CPU在处理指令时,一般需要经过以下几个阶段:




(1) 取指令(IF):根据程序计数器pc中的指令地址,
从存储器中取出一条指令,
同时,pc根据指令字长度自动递增产生下一条指令所需要的指令地址,
但遇到“地址转移”指令时,则控制器把“转移地址”送入pc,
当然得到的“地址”需要做些变换才送入pc。




(2) 指令译码(ID):对取指令操作中得到的指令进行分析并译码,
确定这条指令需要完成的操作,从而产生相应的操作控制信号,
用于驱动执行状态中的各种操作。




(3) 指令执行(EXE):根据指令译码得到的操作控制信号,
具体地执行指令动作,然后转移到结果写回状态。




(4) 存储器访问(MEM):所有需要访问存储器的操作
都将在这个步骤中执行,
该步骤给出存储器的数据地址,
把数据写入到存储器中数据地址所指定的存储单元或者从存储器
中得到数据地址单元中的数据。




(5) 结果写回(WB):指令执行的结果或者访问存储器中得到的数据
写回相应的目的寄存器中。
实验中就按照这五个阶段进行设计,
这样一条指令的执行最长需要五个(小)时钟周期才能完成,
但具体情况怎样?要根据该条指令的情况而定,
有些指令不需要五个时钟周期的,这就是多周期的CPU。


(这段作者MyCodecOdecoDecodE来源CSDN
MyCodecOdecoDecodE/article/details/72675535)


$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$
$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$


IF->ID->EXE->MEM->WB
这五个阶段在CPU内使用的硬件资源是独立的,
也就是某个阶段活跃的时候,
其它阶段是完全空闲的.
既然所有指令都是顺序经过这五个阶段,
那么我们就可以让他们全部永远活跃起来,
也就是指令A的IF阶段刚结束,进入ID阶段时,
IF就又跑起来,马上取了个指令B.
这样,与流水化前,CPU的性能提高了整整五倍!!
天底下真的有这么好的事吗?还真没有.
下节我们会讨论流水线的麻烦事
===============================================================

楼主 savenseg  发布于 2019-03-18 23:36:00 +0800 CST  
===============================================================
【###======
【#CHAPTER[2]-2.8【分支预测】
【###======
分支预测,就是我们在执行分支指令的时候
去预先预测分支指令的分支,
可能预测的了,可能预测不了,


比如:JMPZ n,这样只有两种情况.
这时候CPU可以预先从[n]这段开始提取指令,
这样无论分支判断成功还是失败,
指令队列里都能提供要提取的下一个指令.
但是在流水化的时候,这里可能会产生气泡,
要完全等待JMPZ执行结束才能确定是否跳转,
在JMPZ执行结束前我们根本无法预测JMPZ会不会触发跳转,
这时候要填补这个气泡,我们可以从两种分支路径上选择一个先执行,
如果执行路径是正确的就正确了,不正确还得重新执行另一条路径
的指令,当然也可以同时执行两个路径的指令,只要你的架构够厉害。


然后还有很多更高级的分支预测算法
分支预测有很多办法,在不同架构上表现也不同,
这里只需要发挥你的想象力,想到一个足够好用的办法就行了.
===============================================================

楼主 savenseg  发布于 2019-03-19 00:51:00 +0800 CST  
===============================================================
【###======
【#CHAPTER[2]-2.9【多发射与乱序执行】
【###======
多发射就是一次发射多条指令,
比如以前CPU一次只执行一个指令
现在可以执行三条指令.但是执行三条指令的前提是三条指令
没有相关性,否则就冲突了。
如果有可以消除的相关性,你又得想办法消除.


乱序执行就是修改指令的顺序
比如我们执行下面这个程序:
A
B
C
D
E
其中A和B,C有严重相关,但是D,E两条指令很简单,就算D,E
在A之前执行也没关系,
这样的话把这玩意调度成
D
E
A
B
C
就跑的更快.更改了指令的执行顺序,这就叫乱序.
===============================================================

楼主 savenseg  发布于 2019-03-19 00:51:00 +0800 CST  
===============================================================
【###======
【#CHAPTER[2]-2.11【静态优化,指令融合】
【###======
静态优化是编译器的部分工作之一,
我们可以把静态优化理解为:
把我们的代码扔给一个自动的代码优化程序,
它会帮我们合并冗余的代码并且提前调度好指令顺序,
以让代码更加精简效率更高,
但是有很多优化是静态优化程序发现不了的,
这还得人工手动优化,当然这真的很累,
所以除非关键代码段,没事大概不会摸这个.
或者你最好一开始就把代码写干净点.


指令融合:
有的时候可以把几条指令融合成一条指令执行,
在CPU内部,当然所谓融合出来的新指令,可能是指令集没有给出的,
实际上就是用一个新的执行过程,顺便完成了多条指令的工作.
这个技术有时候会用到,尤其是对于英特尔x86那样的兼容性老破烂.


===============================================================

楼主 savenseg  发布于 2019-03-19 00:52:00 +0800 CST  
===============================================================
【###======
【#CHAPTER[2]-2.12【指令集分类】
【###======
先看百科:词条“指令集架构”
看完了,它还有几个没讲的:
OISC(OneInstructionSetArchitecture):只有一条指令的指令集
ZISC(ZeroInstructionSetArchitecture):无指令指令集
你或许会发现最近的几节全都是百度,
这说明网络资源确实还蛮丰富的,所以建议你们善用搜索引擎.
===============================================================

楼主 savenseg  发布于 2019-03-19 01:05:00 +0800 CST  
精了!

楼主 savenseg  发布于 2019-03-19 11:09:00 +0800 CST  
===============================================================
【###======
【#CHAPTER[3]-3.1【分页内存,虚拟内存与虚拟化】
【###======
之前我们提到的段偏移寻址(CS:IP)方法将内存划分为n个段,
每个段长度是一样的.这种设计即使在不需要通过段偏移
拓展地址空间的CPU上也非常实用,但是要做就要做大的,
我们可以把内存划分为n个页,每个页大小可以不一样,
但是我们一般规定页大小只在几种规格中选择(比如
1kb,2kb,4kb),页可以被安排在内存的任何地方,
但是我们需要存储它的起始位置,
我们可以在内存里存放一个"页表",这样我们提供
页号的时候,它就能给我们页号对应页的起始位置,
还有这个页的一些相关参数.
这样又改变了编程的方式:我们把程序按页划分,
然后给每个页提供一个页号,
比如一个程序被划分成了页0,1,2三个页.


那么支持了页表后CPU是怎么读取东西的?
是这样的:
CPU里和地址存储相关的寄存器现在会保存这两个东西:


页表号n,偏移量s


偏移量s就是PC的值,也就是CPU每读取一条指令都要PC+1也就是s+1,
页表号n表示现在CPU要读取第n个页里的内容.
通过这两个值提取出第n页里第s-1个单元的值,得确保这些:


我们可以通过页表n直接计算出页表项n-1的地址,
一般我们的页表项是按顺序存储,每个页表项占有n个存储单元.
于是我们可以通过这个公式得到第n-1个(从第0项开始数)页表项的地址:


i = b + c( n - 1 )
其中i就是我们要的页表项起始地址,b是第0项的起始地址,
c是表项占有的存储单元数.
页表项的结构通常是这样的:


[页起始地址D,页大小S,其它]
我们可以根据页起始地址和偏移量得到我们要读单元的地址:[D:PC].
并且通过S确定PC的上线,如果PC大于S就说明溢出了.
"其它"这段包括许多其它的设置信息,
比如这个页面是否有效,这个页面的读写访问权限,
这个页面的安全级别,这个页面的所属用户...等等


内存虚拟化是一个重新映射内存地址的东西,它通过一个
地址映射表来实现地址的映射.
比如JMP 54被执行的时候,54通过映射表映射到了75.
那么程序在以为自己正在读取[54]的时候实际上读取了[75],
如果单个映射表不够,我们也可以在映射表项里
写下一个映射表的起始地址,然后在下一个映射表里查找地址,
这里举例是使用了一个二级嵌套映射表的地址查找的实例:


步骤1:给出页表号56
步骤2:查找到映射表A_0第56项,里面的内容是87
步骤3:查找映射表B_87,得到页起始地址.
步骤4:页起始地址与偏移量组合得到实际地址.


可以看到,映射表B_n有很多个,映射表A中项的内容n就告诉我们
要访问第n个映射表B_n


有的时候我们希望能在查找映射表A时就直接得到页起始地址,
可以在页表项加入一个特殊标志位比如P=0或1,
等于0时候意味着这个项提供的是页起始地址,
否则是下一个映射表的编号.


这种多级映射可以一直套娃套过去,但是套越深查找地址就越慢.


为什么要选择内存虚拟化呢:
想象有两个程序,他们被按页分割了程序的部分,那么他们是这样的:
程序A:
代码页0
代码页1
代码页2
数据页3
程序B:
代码页2
数据页1


可以看到程序A使用了页号0,1,2,3.程序B使用了页号1,2.
如果要把他们装入内存,就会发生冲突.
这时A和B都要使用页号1,2.该怎么办?
虚拟内存映射就登场了:
虚拟内存映射可以把程序B的代码页2映射到4,数据页映射到8,
这样程序B还是以为自己在用1,2页,而实际上它在使用页7,8.


当然其实还有静态的解决办法:在把程序B载入内存的时候,
就直接把B中使用页号2,1的地方替换成4,5.
但是这样缺陷太明显了:
每次加载程序时都需要彻底重新更新所有代码.
还有很多缺点就懒得讲了.


另外提一下的是:
通过虚拟映射我们可以把一个页映射到多个程序里,
比如我们的CPU让页0是与IO设备交互的页,
那么程序A和B都要使用IO的时候,
只需要在程序里面创建一个页0,然后让虚拟内存映射机制
把两个程序里的页0都映射到IO页上,就可以了.
也可以让两个程序都映射在一个普通的页面u上,
这样,两个程序对这个页面u进行读写,对方都能知道.
===============================================================

楼主 savenseg  发布于 2019-03-19 14:02:00 +0800 CST  
===============================================================
【###======
【#CHAPTER[3]-3.2【虚拟机,虚拟机监控器】
【###======
啊这个MC用不到的,不讲,感兴趣自己百度.
===============================================================

楼主 savenseg  发布于 2019-03-19 14:09:00 +0800 CST  
第三节蛮短的嘛

楼主 savenseg  发布于 2019-03-19 14:17:00 +0800 CST  
===============================================================
【###======
【#CHAPTER[4]-4.1【CACHE】
【###======
我们之前讨论了大量的处理器性能优化速度,但是有个问题:
存储器的读写效率跟的上处理器的运行速度吗?
我们知道,在MC中,存储器规模越大,它的读写延迟就越高,
这是由于延续信号所需的额外延迟所导致的,
在内存很大的时候,我们不得不面临极其低下的读写性能,
基于这样一个原则:
CPU要提取的下一个指令总是在当前指向地址周围,
被程序控制的数据通常被连续放在一段内存中.
(也就是说,CPU要访问的地址经常是临近的)
CACHE就登场了:


以前我们的CPU是和RAM直接连接的,也就是:
CPU-RAM
但是现在加了一个CACHE,变成了:
CPU-CACHE-RAM
CPU不再能直接访问RAM,CPU直接访问的是CACHE。
那么CACHE是什么呢?


CACHE也是一个存储器,一个很小的存储器,如果内存有1GB,
那么它可能只有4KB(当然MC里肯定达不到这样的数量级),


CACHE以每k个单元为1块划分,将CACHE划分为K个"CACHE块"
然后我们也类似的把RAM也按每n个单元一个块划分,一共划分出了
N个"内存",块编号为0到n-1.


然后,当CPU要访问内存块p中的单元时,先将块p载入到CACHE中,
CACHE有K个CACHE块,我们随便挑一个CACHE块存储内存块p,
然后标记这个CACHE块存储了内存块p,
我们这里假设CACHE块q存储了内存块p


随后,CPU如果想访问内存块p,直接访问CACHE块q就可以了,
而如果CPU之后想访问比如内存块p+3,那还得再重新导入内存块p+3
到某个CACHE块里,


如果所有的CACHE块都存了某个内存块,这时候CPU又要访问一个
新的内存块,而这个内存块并不存在于CACHE中,该怎么办?
只能用简单粗暴的办法:从CACHE中选择一个CACHE块,把它释放(写回到内存里)
并且重新装载CPU要访问的新内存块.不过应该选择释放哪个CACHE块呢?
有很多种选择,比如选择最少被使用的CACHE块,最久没被访问的CACHE块,
并且他们的实现难度也不同,比如如果你只是随机选择一个CACHE块替换,
那电路可以很容易判断释放哪个CACHE块.
(下一节中会对CACHE块释放策略做出更多介绍)


当然我们不能随意释放CACHE块,比如说我们把内存块p映射到了CACHE块q,
那么CPU如果对内存块p中的某个单元写入,它实际上只会写到CACHE中的,
这时候,内存中对应的本体还没有被更新,这就导致了数据的不一致
(CACHE块与对应映射的内存块数据不一致),
这个问题蛮麻烦的,你一样可以有很多的策略可以选择,比如:
在写入CACHE块q的时候,立即同步写入内存块p.
在卸载内存块p对应的CACHE块q时,统一把CACHE块q中的数据覆写到
内存块p中.
他们有不同的优缺点.(下节会介绍更多.)


然后我们回过头来,来考虑一个问题:
我们怎么知道哪个CACHE块存了哪个内存块?


这个问题一样有很多解决方法,我们可以给每个内存块添加几个额外
的存储单元,这几个存储单元负责CACHE的各种存储状态信息,
比如它是空的还是映射了某个内存块,映射了哪个内存块,这个CACHE块
的访问权限,这个CACHE快有没有被修改等等.
也可以在CACHE中,直接添加一个专门用来存各个CACHE块状态信息
的单元块,里面维护着一个存储所有CACHE状态的信息表.


现在让我们来纵观一下CACHE读写的过程:


1.CPU向CACHE发出控制地址为n的单元的请求.
2.判断这个单元所在块在不在CACHE里.
2.1.如果在,直接选中CACHE中的这个块,并且提供一个块偏移量
告诉CACHE这个单元是块中的第i个单元.随后执行对单元的操作.
2.2.如果不在,CACHE会从内存中读取这个块并且存到某个CACHE块中,
如果CACHE中没有空闲块,CACHE还要选择一个块并释放它.


当然,我们只是介绍了CACHE的基本思想,现实中的CACHE很复杂,
例如现实中的CACHE又将每n个块划分为一组,以提高效率.
如果想了解更深入,也可以去网上了解相关介绍和技术文档.
===============================================================

楼主 savenseg  发布于 2019-03-20 15:13:00 +0800 CST  
===============================================================
【###======
【#CHAPTER[4]-4.2【CACHE的优化方法,辅助存储器,颠簸】
【###======
CACHE并不能以如鲁莽的方式优化,
因为它的综合性能,是由命中率,与CPU之间的通讯性能,与RAM
之间的通讯性能,CACHE的各种机制策略等共同决定的.
因为相关技术与MC其实关系不大,而且篇幅较大所以这里我们只提几个MC
里可能用到的(实际上MC里可能还没有人造过这么复杂的CACHE系统):


1.多级缓存:
之前提到的CACHE把CPU和RAM的链接变成了:
CPU-CACHE-RAM


我们还可以把它变成:
CPU-CACHE L1-CACHE L2-RAM,


诸如这样的,就像套娃,也可以多套几个,但是一般只会套
两个,如果是多核心处理器可能会套3个,
一般来说L1比L2小并且块,L2比RAM小并且块,
也就是说越接近CPU的存储器越小但是越快,
如果CPU需要访问单元,会向L1请求,
如果L1没有,L1向L2请求,如果L2也没有,向RAM请求,
当然,如果RAM还没有,如果你的电脑还插了硬盘,会向
硬盘请求,如果再没有?那就是真的没有,这个访问请求肯定有问题.


2.替换策略(也可以用在TLB上):
百度"LRU"


要详细了解CACHE优化,可以参考下面这本书,(其实前言也介绍了)
里面介绍了6种基本优化技术和10种高级优化技术,
并且提供了在现实处理器中对相关技术造成的增益与减益有关的
较详细数据,而且介绍了几种主流的CACHE替换策略.
盗版应该不难找,实体书100块钱对于这本书的内容质量来讲也是相当
划算,如果对这本书比较满意建议买本在家供着.


"《计算机体系结构量化研究方法》第五版,中文版"
(请参阅附页B和第二章)


"《ComputerArchitectureAQuantitativeApproach》6th"
(其实就是上面的那个的英文版,但是是最新的,不过也没加什么新内容)
(请参阅附页B和第二章)


辅助存储器:
如果有很多程序和数据要保存但是不怎么用,
那就要用辅助存储器了,也就是我们电脑里的硬盘(DISK),
这样计算机存储体系就变成了:
CPU-CACHE-RAM-DISK


(DISK比RAM又慢很多但是容量也大很多)
如果CPU需要访问DISK里的东西,RAM里又没有,
就得从DISK里取出来并且放到RAM里,
这方面和CACHE和RAM的关系比较类似,
他们通常是以页为单位替换的,就是之前讲到的页,
在实际设计中,我们给内存分块后,
会让n个块组成一个页,比如8个块组成一个小页,128个块组成一个大页
这也是一种机制,
CACHE和RAM之间交换的是块,RAM和DISK之间交换的是页.


另外还有一个要讲的是颠簸:
主要发生在RAM和DISK之间:
假如有一个程序,不停在页面A和B之间跳,
然后RAM把页面A导入内存中.程序又访问页面B,
如果这时候因为各种原因必须释放内存中的一个页面,
又因为各种原因,RAM选择了释放A,导入B
那么程序跳到B后又跳回A的时候,就又得释放B,导入A,
就这样无限导入释放,就叫颠簸,
因为RAM和DISK之间的通讯很费时间(导入一个页面很慢)
所以对性能影响很大,因此要让系统尽可能的不发生颠簸.
===============================================================

楼主 savenseg  发布于 2019-03-20 16:05:00 +0800 CST  
===============================================================
【###======
【#CHAPTER[5]-5.1【计算机分类】
【###======


Flynn分类法:


一些词的定义:
·指令流:机器执行的指令序列
·数据流:由指令流调用的数据序列,包括输入数据和中间结果
·并行度:指令或数据并行执行的最大可能数目。


根据不同的指令流-数据流组织方式把计算机系统分为4类。


[1]单指令流单数据流[SISD]
就是传统的计算机,比如CHAPTER[1]的那个示例.


[2]单指令流多数据流[SIMD]
SIMD以并行处理机为代表,并行处理机包括多个重复的处理单元.
由单一指令部件控制,


比如说,这里有4个ALU:ALU0到ALU3,4个GPRs:GPRs0到GPRs3
4个ALU分别与4个GPRs相对应,但是CPU只有一个指令译码器,
并且会把指令的操作广播到四对ALU和GPRs上,


一个指令被执行多次,并且指令的每个操作
相互之间没有关系,这就是SIMD.


比如执行ADD R1,R2,R3。
那么每个GPRs的R1都分别被它们的R2和R3之和覆写,
但是我们也可以只用一个ALU,分别连续对四个GPRs中的寄存器
进行ADD R1,R2,R3,这也是SIMD的一般做法(否则太多ALU
会很占空间)




[3]多指令流单数据流[MISD]
MISD的结构,它具有n个处理单元,
按n条不同指令的要求对同一数据流及其中间结果进行不同的处理。
一个处理单元的输出又作为另一个处理单元的输入。
(一般来说吃饱了没事不会搞MISD的计算机)


[4]多指令流多数据流[MIMD]
比如多核心计算机.
===============================================================

楼主 savenseg  发布于 2019-03-20 16:29:00 +0800 CST  
===============================================================
【###======
【#CHAPTER[5]-5.2【向量处理器】
【###======
向量处理器的一条指令能处理n对元素之间的运算,这里
拿向量指令跟普通指令做个比较:
现在我们要做4个加法用分别用ADD和_4VADD实现:
(_4VADD就是向量化的加法指令,可以让两个4元素向量相加)


用ADD写:
ADD R1,R2
ADD R3,R4
ADD R5,R6
ADD R7,R8


用_4VADD写:
_4VADD V1,V2


没错一行就搞定了,上面的V1和V2是向量寄存器,
向量寄存器能存n个元素,在这个例子中向量寄存器存了4个元素.
具体_4V1ADD V1,V2的效果是这样的:
[V1_0,V1_1,V1_2,V1_3] = [V1_0,V1_1,V1_2,V1_3] + [V2_0,V2_1,V2_2,V2_3]


在硬件层次上,可能是向量处理器在一个ALU上连续执行了四次
加法运算,也可能是在四个ALU上分别执行了一次加法运算,
具体取决于向量处理器的运算硬件数目和资源分配.


那么如果在上面那个例子中,一个向量寄存器既然存了4个元素,
我又指向让三个分量运算,怎么办呢?
很简单,在向量处理器中可以提供"遮罩向量",遮罩向量
也有n个分量分别对应着向量寄存器的分量,遮罩向量的分量为1或0,
这样,如果我们使用遮罩向量,可以让运算器不执行对应遮罩向量分量
值为0的元素的有关运算(或者屏蔽运算结果,也就是不写回),
遮罩向量存储于遮罩向量寄存器里,然后向量寄存器的分支指令
也可以根据遮罩向量的值判断,


具体更多可以百度.
===============================================================

楼主 savenseg  发布于 2019-03-20 21:02:00 +0800 CST  

楼主:savenseg

字数:34097

发表时间:2019-03-16 19:01:00 +0800 CST

更新时间:2019-09-05 16:17:56 +0800 CST

评论数:350条评论

帖子来源:百度贴吧  访问原帖

 

热门帖子

随机列表

大家在看