重点:python
ALU
:
做用:输出二进制,它会执行计算。算法
两种内存:
寄存器: 很小的一块内存,能存一个值。RAM
: 是一大块内存,能在不一样地址存大量数字。 (寄存器增大后改形成RAM
)编程
把RAM
, 寄存器
, ALU
放在一块儿,组件计算机的心脏CPU
(中央处理单元)。缓存
拼个CPU
CPU: 四个寄存器 + 2个寄存器(指令寄存器和指令地址寄存器) + RAM + ALU(指令须要其功能才使用) + “时钟”架构
CPU
做用: 负责执行程序。
程序由一个个操做组成,这些操做叫作“指令”,由于它们“指示”计算机要作什么。若是是数学指令,好比加/减,CPU
会让ALU
进行数学计算。也多是内存指令,CPU
会和内存通讯,而后读/写值。CPU
里有不少组件。重点放在功能,而不是一根根线具体怎么连。当用一根线链接两个组件时,这条线只是全部必须线路的一个抽象。这种高层次视角叫“微体系架构”。性能
首先要内存,使用RAM
,为了保持简单,假设它只有16个位置,每一个位置存8位。再来四个8位存储器,叫A, B, C, D。寄存器用来 临时存数据 和 操做数据。fetch
数据是以二进制存在内存里。
程序也能够存在内存里。优化
能够给CPU
支持的全部指令,分配一个ID:ui
在这个假设的例子,用前四位存“操做代码”(operation code),简称“操做码”(opcode),后四位表明数据来自哪里,能够是寄存器或内存地址。加密
还须要两个寄存器,来完成CPU
。
当启动计算机时,全部寄存器从0开始。
CPU的第一个阶段叫“取指令阶段”(FETCH PHASE),负责拿到指令。
首先,将“指令地址寄存器”连到RAM
,寄存器的值为0,所以RAM
返回地址0的值,0010 1110
会复制到“指令寄存器”里。
如今指令拿到了,要弄清楚是什么指令,才能执行(execute),而不是杀死(kill)它,这是“解码阶段”。
前4位0010
是LODA A
指令,意思是,把RAM
的值放入寄存器A。
后4位1110
是RAM
的地址,转成十进制是14。
接下来,指令由“控制单元”进行解码,就像以前的全部东西,“控制单元”也是逻辑门组成的。
好比:为了识别“LOAD A”指令,须要一个电路,检查操做码是否是0010
,能够用不多的逻辑门来实现。
指令:
知道了什么是指令,就能够开始执行了,开始“执行阶段”。
RAM
的“容许读取线”,把地址14传过去,RAM
拿到值,0000 0011
,十进制的3。LOAD_A
指令,想把这个值只放到寄存器A,其它寄存器不受影响,因此须要一根线,把RAM
链接到4个寄存器,用“检查是否LOAD_A指令的电路”启用寄存器A的“容许写入线”。成功把RAM
地址14的值,是十进制的3,放到了寄存器A:
CPU怎么执行命令?
“取指令 -> 解码 -> 执行”
指令完成了,能够关掉全部线路:
去拿下一条指令,“指令地址寄存器”+1,“执行阶段”结束。
LOAD_A
只是CPU能够执行的各类指令之一,不一样指令由不一样逻辑电路解码,这些逻辑电路会配置CPU内的组件来执行对应操做。
如今计算机支持更多指令,直接操做内存,但原理上是如此。
复杂的逻辑电路解码,把它封装成一个总体的“控制单元”。
汇编指令与机器指令就是一一对应。
控制单元就像管弦乐队的指挥,“指挥”CPU的全部组件。“取指令 -> 解码 -> 执行”完成。
后续能够再从“取指令”开始:
RAM
返回地址1里的值:0001 1111
。0001
是LOAD_B
指令,从RAM
里把一个值复制到寄存器B,此次内存地址是1111
,十进制的15。RAM
读地址15,并配置寄存器B接受数据。0001 1110
也就是十进制的14存到了寄存器B。后续能够再从“取指令”开始:
RAM
返回地址1里的值:1000 0100
。RAM
地址,而是2位,2位,分别表明2个寄存器,2位能够表示4个值(00, 10, 01, 11),因此足够表示4个寄存器。1000 0100
,表明把寄存器B,加到寄存器A里。ADD
指令,“控制单元”会,启用寄存器B,做为ALU的第一个输入,还启用寄存器A,做为ALU的第二个输入。ALU能够执行不一样操做,因此控制单元必须传递ADD操做码告知ALU要作什么。0001 0001
。ADD
结束。后续能够再从“取指令”开始:
RAM
返回地址3里的值:0100 1101
。STORE A
指令(把寄存器A的值放入到内存)RAM
地址13中。RAM
,但此次不是“容许读取”而是“容许写入”;同时,打开寄存器A的“容许读取”,这样就能够把寄存器A里的值,传给RAM
。运行第一个电脑程序:
它从内存中加载两个值,相加,而后把它结果放回到内存中。
人工切换CPU的状态:“取指令 -> 解码 -> 执行”。
最后:STORE_A 13
指令执行完以后,再执行HALT
指令。不然CPU会不停运行下去,处理后面的0。由于0不是操做码,因此电脑会崩掉。
通常来讲RAM
没写的话,里面会储存乱码而不是0(不HALT
就会崩掉)。
时钟
是“时钟”来负责管理CPU的节奏,时钟以精确的间隔,触发电信号,“控制单元”会用这个信号,推动CPU的内部操做。就像罗马帆船的船头,有一我的负责按节奏的击鼓,让全部划船的人同步。就像节拍器同样。
节奏不能太快,由于就算是电也要必定时间来传输,CPU“取指令 -> 解码 -> 执行”的速度叫“时钟速度”。单位是赫兹,赫兹是用来表示频率的单位。1赫兹表明一秒1个周期。
前面的步骤:读取 -> 读取 -> 相加 -> 存储
时钟速度大概是0.03赫兹,(1/360 = 0.03Hz)。
第一个单芯片CPU是“英特尔 4004”1971年发布的4位CPU。
它的微架构:
虽然是第一个单芯片的处理器,但它的时钟速度达到了740千赫兹 - 每秒74万次。
一兆赫兹是1秒1百万个时钟周期,如今的电脑或手机,确定有几千兆赫兹。1秒10亿次时钟周期。
计算机超频,意思是修改时钟速度,加快CPU的速度。芯片制造商常常会给CPU留一点余地,能够接受一点超频。但超频太多会让CPU过热,或产生乱码,由于信号跟不上时钟(超频通常不会让计算机起火)。
降频,有时候没有必要让处理器全速运行,可能用户走开了,或者在跑一个性能要求较低的程序,把CPU的速度降下来,能够省不少电。省电对用户电池的设备很重要,好比笔记本和手机。
为了尽量省电,不少现代处理器能够按需求,加快或减慢时钟速度。这叫“动态调整频率”。加上时钟后,CPU才是完整的。
把寄存器
和ALU
和时钟
封装在一块儿:
RAM
,是在CPU
外边的独立组件。CPU
和RAM
之间用“地址线”,“数据线”和“容许读/写线”进行通讯。
重点:运行一遍程序。
LOAD_A
, LOAD_B
, SUB
, JUMP
, ADD
, HALT
等指令。JUMP NEGATIVE
是负数才跳转,还有其余类型的JUMP
。把ALU
, 控制单元, RAM
, 时钟结合在一块儿,作了一个基本,但可用的“中央处理单元”,简称CPU。它是计算机的核心。CPU之因此强大,是由于它是可编程的,若是写入不一样指令,就会执行不一样任务。CPU是一块硬件,能够被软件控制。
介绍指令集
LOAD_A
寄存器ALOAD_B
寄存器BADD
相加。STORE_A
存储寄存器A,并写入RAM
中。SUB
减法,和ADD
同样也要2个寄存器来操做。JUMP(跳转)
,让程序跳转到新位置。若是想改变指令顺序,或跳过一些指令,这个就很实用。例如:JUMP 0
,能够调回开头。JUMP
在底层的实现方式是:把指令后4位表明的内存地址的值覆盖掉“指令地址寄存器”里的值。 JUMP_NEGATIVE
,它只在ALU的“负数标志”为真时,进行JUMP
。算数结果为负,“负数标志”才是真,结果不是负数时,“负数标志”为假,若是是假,JUMP_NEGATIVE
就不会执行程序照常运行。HALT(中止)
,计算机还须要知道何时该停下来。条件跳转
指令和数据都是存在同一个内存里的。它们在根本层面上毫无区别,都是二进制数。 HALT
很重要,能区分指令和数据。
JUMP
让程序更完整一些:
如今从CPU的视角走一遍程序。
LOAD_A 14
,把1存入寄存器A(地址14里值是1)。LOAD_B 15
,把1存入寄存器B(地址15里值是1)。ADD B A
把寄存器B和A相加,结果放到寄存器A里。如今寄存器A的值是2,(以二进制存储的)STORE_A 13
指令,把寄存器A的值存入内存地址13。JUMP 2
指令,CPU会把“指令寄存器”的值,如今的“4”,改为“2”。所以下一步不是HALT
。ADD B A
,寄存器A里的2,寄存器B里的1。1+2=3,寄存器A变成3。存入内存。又碰到JUMP 2
,回到ADD B A
,1+3=4。每次循环都+1
,不断增多。若是按照这样执行下去,永远不会碰到HALT
,老是会碰到JUMP 2
。这个叫无限循环(INFINITE LOOP)∞。JUMP 2
指令:
为了中止下来,须要有条件的JUMP
,只有特定条件知足了,才执行JUMP
。
好比:JUMP NEGATIVE
就是条件跳转的一个例子。
还有其它类型的条件跳转,好比,JUMP IF EQUAL(若是相等)
,JUMP IF GREATER(若是更大)
。
for
循环是有个数限制,while和python里的repeat是无限循环。
执行下图程序:
SUB B A
,用A减B,11-5=6。如今寄存器A的值是6JUMP_NEG 5
,ALU上次计算的是6,是正数,因此“负数标志”是假。所以处理器不会执行JUMP
,继续下一条指令。JUMP 2
,JUMP 2
没有条件,直接执行SUB B A
, 6-5=1。如今寄存器A的值是1。JUMP_NEG 5
,由于1仍是正数,所以JUMP NEGATIVE
不会执行,来到下一条指令。JUMP 2
,直接执行SUB B A
, 1-5=-4。如今寄存器A的值是-4。此次ALU
的“负数标志”是真。如今寄存器A的值是-4。JUMP_NEG 5
, CPU的执行跳转到内存地址5的指令ADD B A
。跳出了无限循环。ADD B A
,-4+5=1,存入寄存器A,如今寄存器A的值是1。STORE_A 13
,存入内存地址13HALT
指令,中止程序。指令顺序:
LOAD_A 14
值是11LOAD_B 15
值是5SUB B A
寄存器A值6(11-5)JUMP_NEG 5
"负数标志"falseJUMP 2
跳转会地址2再次依次执行SUB B A
寄存器A值1(6-5)JUMP_NEG 5
"负数标志"falseJUMP 2
跳转会地址2再次依次执行SUB B A
寄存器A值-4(1-5)JUMP_NEG 5
"负数标志"trueADD B A
寄存器A值1(-4+5)STORE_A 13
存入内存地址13HALT
指令,程序结束这段程序只有7个指令,但CPU执行了13个指令。由于在内部循环了2次。
这段代码是算余数,11除5余1 -> 11-5-5-5+5 = 1
软件能够作到硬件作不到的事。ALU
可没有除法功能,是程序给了除法这个功能。
假设的CPU很基础,全部指令都是8位,操做码只占了前4位,即使用尽4位,也只能表明16个指令。并且有几条指令,是用后4位来指定内存地址。只能表示16个指令,并很少,甚至不用使用JUMP 17
,由于4位二进制没法表示数字17,所以,真正的现代CPU用两种策略。
HALT
指令,HALT
不须要额外数据,那么会立刻执行。若是看到JUMP
,它得知道位置值,这个值在JUMP
的后边。这个叫“当即值”,这样设计,指令能够是任意长度。但会让读取阶段复杂一些。某些指令不须要操做内存因此能够省下内存那四位。
可是JUMP
也须要4位操做码,这样仍是只有4位来表示内存地址。
通常来讲 (指令=操做码+操做值地址)。当(指令=操做码+操做值)时,这个操做值就是当即值。
这样大于8位的地址,能够经过屡次重复读取的方式得到,这也是读取阶段相对麻烦的地方。
当即值是指令+位置
英特尔4004处理器
1971年,英特尔发布了4004处理器。这是第一次把CPU作成一个芯片,给后来的英特尔处理器打下基础。
JUMP
, ADD
, SUB
, LOAD
JUMP
,以表示更多内存地址。处理器从1971年到如今发展巨大,现代CPU,好比英特尔酷睿i7,有上千个指令和指令变种。长度从1到15个字节。
例如:光ADD
指令就不少变种。
指令愈来愈多,是由于给CPU设计了愈来愈多功能。
从1秒1次运算,到如今千兆赫甚至兆赫的CPU。如今看视频的设备也有GHz
速度。
1秒10亿条指令,这是很大的计算量。
减小晶体管切换时间
早期计算机的提速方式是,减小晶体管的切换时间。
晶体管组成了逻辑门,ALU
以及其它计算机组件。
这种提速方法最终会碰到瓶颈,因此处理器厂商,发明了各类新技术来提高性能,不但让简单指令运行更快,也让它能进行更复杂的运算。
除法的程序,给CPU执行,方法是作一连串减法。好比16除4会变成。16-4-4-4-4
,碰到0或负数才停下来。这种方法要多个时钟周期,很低效。因此现代CPU直接在硬件层面设计了除法,能够直接给ALU除法指令。
除法电路
拥有除法的ALU更大也更复杂一些。但也更厉害,复杂度vs速度 的平衡在计算机发展史上常常出现。
例如:现代处理器有专门电路来处理图形操做,解码压缩视频,加密文档等等。若是用标准操做来实现,要不少个时钟周期。可能据说过处理器MMX
, 3DNOW
, SEE
。它们有额外电路作更复杂的操做。用于游戏和加密等场景。
指令不断增长,一旦习惯了它的便利就很难删掉,因此为了兼容旧指令集,指令数量愈来愈多。
英特尔4004,第一个集成CPU,有46条指令,足够作一台能用的计算机。但现代处理器有上千条指令,有各类巧妙复杂的电路。
超高的时钟速度带来另一个问题: 如何快速传递数据给CPU。就像有强大的蒸汽机,但没法快速加煤。RAM
成了瓶颈。
RAM
是CPU以外的独立组件,意味着数据要用线来传递,叫“总线”。
总线可能只有几厘米,别忘了电信号的传输接近光速。但CPU每秒能够处理上亿条指令。很小的延迟也会形成问题。
给CPU加缓存
RAM
还须要时间找地址,取数据,配置,输出数据。一条“从内存读数据”的指令可能要多个时钟周期,CPU空等数据。解决延迟的方法之一是:给CPU加一点RAM,叫“缓存”。
由于处理器里空间不大,因此缓存通常只有KB
或MB
,而RAM
都是GB
起步。
缓存提升了速度,CPU从RAM
拿数据时,RAM
不用传一个,能够传一批。虽然花的时间久一点,但数据能够存在缓存。
这很实用,由于数据经常是一个个按顺序处理。
例如:算餐厅的当日收入。先取RAM
地址100的交易额,RAM
与其只给1个值,直接给一批值。把地址100到200都复制到缓存,当处理器要下一个交易额时,地址101,缓存会说:“我有下一个交易额的值”,而不用向RAM
取数据。
由于缓存离CPU近,一个时钟周期就能给数据,CPU不用空等。
比反复去RAM
拿数据快得多,若是想要的数据已经在缓存,叫“缓存命中”。若是想要的数据不在缓存,叫“缓存未命中”。
缓存也能够当临时空间,存一些中间值,适合长/复杂的运算。
若是计算完餐厅一天销售额,想把结果存到地址150,就像以前,数据不是直接存到RAM
,而是存在缓存,这样不但存起来快一些,若是还要接着算,取值也快一些。
但这样带来一个问题,缓存和RAM
不一致了,这种不一致必须记录下来,以后要同步。所以缓存里每块空间,有一个特殊标记,叫“脏位(Dirty bit)”。
同步通常发生在,当缓存满了而CPU又要缓存时,在清理缓存腾出空间以前,会先检查“脏位”,若是是“脏”的,在加载新内容以前,会把数据写会到RAM
。
流水线设计
提高性能方法叫“指令流水线”。
例如:洗一整个酒店的床单,但只有1个洗衣机, 1个干燥机。
选择1: 按顺序来,放洗衣机等30分钟洗完,而后拿出湿床单,放进干燥机等30分钟烘干。这样1小时洗一批。
须要用“并行处理”进一步提升效率,先放一批床单到洗衣机,等30分钟洗完,而后湿床单放进干燥机,但此次,与其干等30分钟烘干,能够放另外一批进洗衣机。让两台机器同时工做,30分钟后,一批床单完成,另外一批完成一半,另外一批准备开始,效率是翻倍的。
处理器也能够这样子设计,CPU是按序处理,取指令(fetch) -> 解码(decode) -> 执行(execute)
,不断重复。这种设计,三个时钟周期执行1条指令。
并行处理
但由于每一个阶段用的是CPU的不一样部分,意味着能够并行处理,“执行”一个指令时,同时能够“解码”下一个指令,“读取”下下个指令。不一样任务重叠进行,同时用上CPU里全部部分。这样的流水线,每一个时钟周期执行1个指令,吞吐量*3。
和缓存同样,这也会带来一些问题:
JUMP
指令会停一下子等待条件值肯定下来,一旦JUMP
的结果出来了,处理器就继续流水线。但由于空等会形成延迟,因此高端处理器会用一些技巧(调度算法,冒险分支)。JUMP
想象成是“岔路口”,高端CPU会猜哪条路的可能性大一些,而后提早把指令放进流水线,这叫“推测执行(speculative execution)”。当JUMP
的结果出了,若是CPU
猜对了,流水线已经塞满正确能够执行的指令,能够立刻运行。若是CPU
猜错了,就要清空流水线,就像走错路掉头。多个ALU
理想状况下,流水线一个时钟周期完成1个指令,而后“超标量处理器”出现了,一个时钟周期完成多个指令。即使有“流水线设计”,在指令执行阶段,处理器里有些区域仍是可能会空闲,好比,执行一个“从内存取值”指令期间ALU
会闲置。因此一次性处理多条指令(取指令+解码)会更好。
能够再进一步,加多几个相同的电路,执行出现频次很高的指令。(一核有难,多核围观)
例如:不少CPU有四个,八个甚至更多彻底相同的ALU
。能够同时执行多个数学运算。
目前说过的方法:“缓存”,“指令流水线”,“多个ALU”,都是优化1个指令流的吞吐量。
多核(Code)
另一个提高性能的方法是:同时运行多个指令流,用多核处理器。(七核看戏)
双核或四核处理器,意思是一个CPU芯片里,有多个独立处理单元。很像是有多个独立CPU,但由于它们整合紧密,能够共享一些资源。好比缓存,使得多核能够合做运算。但多核不够时,能够用多个CPU。
高端计算机,视频观看的计算机,须要更多的马力,让上百人以上可以同时流畅的观看,2个或4个CPU是最多见的,但有时有更高的性能需求,因此造了超级计算机。
好比模拟宇宙的造成,须要强大的计算能力。给普通的台式机加几个CPU没什么用,须要更多的处理器。目前为止,最快的计算机在中国无锡的“神威 太湖之光”,有40960个CPU,每一个CPU有256和核心,总共超过1千万个核心,每一个核心的频率是1.45GHz,每秒能够进行9.3亿亿次浮点运算。
现代的处理器不但大大的提升了速度,并且也变得更复杂,用各类技巧,榨干每一个时钟周期,作尽量多的运算。