计算机科学速成课笔记(1-9)

目录

布尔逻辑&逻辑门

二进制

算术逻辑单元 

寄存器&内存

中央处理器(CPU)

指令&程序

高级CPU设计

原视频见b站CrashCourse字幕组【计算机科学速成课】


布尔逻辑&逻辑门

NOT:布尔值反转

 AND

 OR

 

 XOR


二进制

1 byte = 8bits

Mega is a million bytes(MB),giga is a billion bytes (GB), terabyte (TB) is a trillion bytes or 8 trillion bits.

在二进制里,1000字节=2^10字节=1024字节,1000也是KB的正确单位

在32位的浮点数中,第一位表示数字正负,接下来的八位存储指数,剩下23位存有效位数


算术逻辑单元 

ALU(arithmetic logic unit),算术逻辑单元,包含两个单元:算术单元和逻辑单元

算术单元负责计算机里的所有数字操作,例如加减法、增量计算等

半加器

 全加器

 制作八位加法器

         最后一位全加器有“进位”的输出,代表两个数字的和太大了,超过了用来表示的位数,称之为溢出

        ALU一般支持这8个操作,没有乘法和除法,因为简单的ALU没有专门的电路来处理而是把乘法用多次加法来实现

 逻辑单元,执行逻辑操作,比如AND,OR和NOR操作

用符号表示ALU

        “Operation code”,例如1000 = 加法,1100 = 减法。“Flags”是1位的,代表特定状态,普遍使用三种。例如相减两个数字结果为0时,将“ZERO”设置位“TRUE(1)”,


寄存器&内存

RAM,随机存取储存器(Random Access Memory),只能在有电的情况下存储东西

另一种存储叫持久存储(persistant memory),电源关闭时数据不会丢失

存“1”
存“0”

         “SET”的输入将输出变为1,“RESET”的输入将输出变为0。如果两个输入都为0,电路会输出最后放入的内容。这称为“latch(锁存)”,因为它“latches onto(锁定)”了一个值,放入数据的动作叫“写入”,拿出数据的动作叫“读取”

AND-OR 锁存器

        为了更容易使用,我们希望只有一条输入线,将它设置为0或1来存储值,还需要一根线来“启用”内存,启用时允许写入,没启用时就“锁定”,这条线叫“the write enable line (允许写入线)”,再加一些额外逻辑门,可以做出这个电路,称之为“Gate Latch (门锁)”,因为门用来打开和关上

        一组这样的锁存器叫“寄存器(register)”,能存一个数字,这个数字有多少位,叫“位宽(width)”,有8位、16位、32位、64位等。例如8位寄存器,写入寄存器前,要先启用里面所有的寄存器,可以用一根线连接所有的“允许输入线”,把它设置为1,然后用8条数据线发数据,再将“允许输入线”设回0。如果只有很少的位,把锁存器并排放置也勉强够用了。64位寄存器要64根数据线,64根连到输出端

 但由于64位时需要129(64*2+1)根线,256位时需要513(256*2+1)根线,解决方法就是矩阵,在矩阵中,将锁存器排列成网格。在存256位时,用16*16网格的锁存器, 要启用某个锁存器,就打开相应地的行、列

当我们只想打开交叉处锁存器的“允许写入线”,其他所有寄存器保持关闭,用AND门来实现。只有行和列均为1时,AND门才输出1。这种锁存器用一根“允许写入线”连接所有的锁存器,为了锁存器可以写入,此时行和列的“允许写入线”都必须是1,每次只有一个锁存器会这样,这代表着可以用一根“数据线”连接所有锁存器来传数据,因为只有一个锁存器会启用,只有那个会存数据。可以用类似的技巧,做“允许读取线”来读数据,从一个指定的锁存器读取数据。这样,对于256位的存储,只要35条线,1条“数据线”,1条“允许写入线”,1条“允许读取线”,还有16行、16列的线。

 由于只有16行、列,只需要4位来表示地址即可。例如“第12行,第8列”就可以写成“11001000”,再将地址转化为行和列,需要“多路复用器(multiplexer)”,它有很多种大小,因为有16行,现在需要1到16多路复用器。输入一个4位数字,它会把那根弦连接到相应的输出线,例如输入“0000”,则选择第一列。

这样,256位的存储

 但是256位的存储也没法做什么事情,需要扩大规模,先并排放置类似寄存器那样,一行8个,可以存8位数字,也就是1个字节。为了存一个8位数字,同时给8个256位存储一样的地址,每个地址存1位,意味着这里总共能存256个字节(256*8 bits)

        为了简单,不管内部,不看作是一堆独立的存储模块和电路,而是看成一个整体的可寻址内存,我们有256个地址,每个地址能读或写一个8位值。这是一块“SRAM(静态随机抽取存储器)”


中央处理器(CPU)

        中央处理器(CPU),负责执行程序,程序由一个个操作组成,这些操作叫“指令”,因为它们“指示”计算机要做什么。CPU会和内存通信,然后读/写值。

        现在开始组件CPU,当用一条线连接两个组件时,这条线只是所有必须线路的一个抽象。这种高层次的视角叫“微体系架构”。为了保持简单,假设RAM只有16个位置,每个位置存8位,再来四个八位寄存器,叫做A, B, C, D,用来临时存数据和操作数据。我们知道数据存在内存里,程序也可以存在内存里,给CPU支持的所有指令分配一个ID,在这个假设的例子里,我们用前四位来存“操作代码(operation code)”,或者称为“opcode”,后四位数据代表来自哪里,可以是寄存器或内存地址。我们还需要两个寄存器来完成CPU,①一个用来追踪程序运行到哪里了,叫“指令地址寄存器”,顾名思义存当前指令的内存地址;②另一个存当前指令,叫“指令寄存器”。

        当启动计算机时,所有寄存器从0开始。为了举例,在RAM里放了一个程序用来过一遍流程。

①CPU的第一个阶段,叫“取指令阶段(Fetch Phase)”,负责拿到指令:首先将指令地址寄存器连接到RAM,寄存器的值为“0000 0000”,因此RAM返回地址0的值,“00101110”会复制到指令寄存器里;

②CPU的第二阶段,叫“解码阶段(Decode Phase)”:现在拿到了指令,要弄清楚是什指令才能执行。刚刚拿到的值前四位“0010”是LOAD A的指令,即把RAM的值放入寄存器A,后四位“1110”是RAM的地址,即十进制的14。接下来指令由“控制单元”进行解码,就像之前的所有东西,“控制单元也是逻辑门组成的,比如,为了识别“LOAD_A”指令,需要一个电路检查操作码是不是0010,可以用很少的逻辑门来实现。

③CPU的第二阶段,叫“执行阶段(Execute Phase)”:用“检查是否LOAD_A指令的电路”,可以打开RAM的“允许读取线”,把地址14传过去,RAM拿到值“0000 0011”,因为LOAD_A指令,需要把值放到寄存器A里,其他寄存器不受影响,我们需要一根线,把RAM的“数据线”连接到四个寄存器上,用“检查是否LOAD_A指令的电路”连接到寄存器A的“允许写入线”,然后就成功了。既然指令成功了,可以关掉所有的线路,去拿下一条指令。将指令地址寄存器的值+1,即“0000 0001”执行阶段就此结束

可以把控制单元看作一个整体,更简洁一些

指令地址寄存器中为“0000 0001”时,是将“0000 1110”写入到寄存器B中。

        指令地址寄存器中为“0000 0010”时,RAM返回“1000 0100”,此时操作码为“ADD(1000)”,后四位不是RAM地址,而是2位2位的寄存器ID,2位可以表示4个寄存器。“0100”的第一个地址“01”代表寄存器B,第二个地址“00”代表寄存器A,所以“1000 0100”代表把寄存器B里的值加到寄存器A里,为了执行这个命令,需要整合ALU。“控制单元”负责选择正确的寄存器作为输入,并配置ALU执行正确的操作。对于ADD指令,“控制单元”会启用寄存器B作为ALU的第一个输入,还启用寄存器A作为ALU的第二个输入,控制单元传递ADD操作码告诉ALU要进行的操作,最后输出的结果应该保存到寄存器A中,但不能直接写入寄存器A,这样新的值进入到ALU时,不断和自己相加,因此控制单元用一个自己的寄存器暂时保存结果,再关闭ALU,然后把值写入正确的寄存器。这里“3+14=17”,二进制为“0001 0001”现在存到了寄存器A里

        再对指令地址寄存器+1,变为“0000 0011”,对应的指令时“0100 1101”,操作码“0100”时STORE_A即把寄存器A的值放入内存,RAM的地址是“13”.接下来把地址传给RAM,这次是“允许写入线”,同时打开寄存器A的“允许读取”,这样就可以把寄存器A里的值传给RAM

        CPU中有负责管理节奏的“时钟”,时钟以精确的间隔,触发电信号,控制单元再用这个信号推进内部操作。因此CPU“取指令→解码→执行”的速度叫“时钟速度”,单位是Hz(s/period),现在很多处理器可以按需求加快或减慢时钟速度,这叫“动态调整频率”。加上时钟以后,CPU就完整了(RAM是CPU外面独立的组件)


指令&程序

CPU之所以强大,是因为其可编程性,所以CPU是可以被软件控制的硬件。

在上一章的基础上继续添加指令,“SUB”,“JUMP”,“JUMP_NEG”,“HALT”

"JUMP"让程序跳转到新的位置,如果想改变指令顺序,或跳过一些指令,这个很实用;

“JUMP_NEG”它只在ALU的结果的"负数标志"为真时,进行跳跃

“HALT”计算机还需要知道什么时候该停下来,所以才有HALT指令,它能区分指令和数据

instructiondescriptionaddress and registers
SUBSubtract two registers,store value into second register

2-bits register ID,

2-bits register ID

JUMP

Update instruction address register to new address

(i.e. jump to address)

4-bits memory address
JUMP_NEGIf ALU result was negtive,update instruction address register to new address4-bits memory address
HALT

Prohram done.

Hailt computer.

NA

这样就可以设计一个做除法的程序,方法是做一连串的减法,比如16/4变成“16-4-4-4-4” ,碰到0或负数才停下,但这种方法要多个时钟周期,很低效。       

由于此时CPU所有指令只有8位,操作码只占了前面4位,也只能代表16个指令,我们只能操作16个地址,显然不够。现代更直接的解决方法时用更多位来表示指令,比如32位或64位,这些叫做“指令长度”,第二种策略是“可变指令长度”,不等长的指令操作码可以减小操作码的平均长度,提高指令编码的效率,从指令的扩展性来看,也希望操作码长度可变,此时会产生“立即数”。


高级CPU设计

早期计算机的提速方式是减少晶体管的切换时间,晶体管组成了逻辑门,ALU以及其他组件。

由上一章低效的做除法的例子,现在CPU直接在硬件层面设计了除法直接给ALU除法指令,随着越来越多的指令集,超高的时钟速度带来另一个问题——如何快速传递数据给CPU?RAM成为了瓶颈。RAM是独立于CPU之外的组件,就意味着数据要用线来传递,叫“总线”。总线可能只有几厘米,但CPU每秒需要处理上亿条指令,很小的延迟都会造成问题。再者RAM还需要时间找地址、取数据、配置、输出数据,导致一条指令都可能要耗费多个时钟周期,CPU空等数据。

1、缓存cache

解决延迟的方法之一就是给CPU加一点RAM,即“缓存(cache)”。

因为处理器的空间不大,所以缓存一般只有KB或MB,而RAM都是GB起步的。有了缓存之后,CPU从RAM拿数据时,不再是传一个而是一批,虽然花的时间久一点,但是数据可以保存到缓存里。因为缓存离CPU更近了,一个周期就能给出数据,这样一来CPU就不用空等,比反复取RAM拿数据快得多。

如果想要的数据已经在缓存里,叫做“cache hit”,如果想要的数据不在缓存里,叫做“cache miss”。缓存也可以当临时空间,存在一些中间值,适合更长、更复杂的运算。在计算出结果之后想要写入RAM,数据没有直接写入RAM,而是放到缓存里,方便再利用,但是会出现一个问题——缓存和RAM不一致了。这种不一致必须记录下来,之后需要同步,因此缓存里每块空间都有一个特殊标记,叫“dirty bit”。同步一般发生在当缓存存满了,而CPU又要缓存时,在清理缓存腾出空间之前,会先检查“dirty bit”。如果这个标记是“dirty”,在加载新内容之前,会把数据写回RAM。

2.指令流水线instruction pipeline

另一种提升性能的方法叫“指令流水线(instruction pipelining)”,即用“并行处理(parallelize)”提高效率。CPU的三个运行阶段使用并行处理时,吞吐量有三倍的提高。和缓存一样,这也会带来一些问题。第一个问题:指令之间的依赖关系,例如在读取某个数据时,同时出现指令改写这个数据,最终导致拿到的是一个旧数据。因此流水线处理时要先弄清楚数据依赖性,必要时通知流水线,避免出问题。高端CPU会更进一步动态排序有依赖关系的指令,从而最小化流水线的停工时间,这叫“乱序执行(out-of-order execution)”。

 第二个问题:“条件跳转(conditional jump instructions)”,这些指令会改变程序的执行流,简单的流水线CPU在看到JUMP指令会停一会儿等待条件值确定下来,一旦JUMP结果出来了处理器就继续流水线,这会造成CPU空等或延迟。高端的CPU会用一些技巧来处理这个问题:可以把JUMP看成是“a brunch岔路口”,CPU猜测它走哪条路的可能性更大一些,然后提前把这条路上的指令放进流水线,这叫“推测执行speculative execution”,如果猜测结果正确,然后就马上执行指令,如果猜错了则需要清空流水线,就像“走错路掉头pipeline flush”。为了尽可能减少清空流水线的次数,厂商们研发了复杂的方法来猜测哪条分支更有可能,叫“分支预测(branch prediction)”。

 在理想情况下,流水线一个时钟周期完成一个指令,然后出现了“超标量处理器(superscalar)”,一个时钟周期完成多个指令。即便有流水线设计,在指令阶段仍然有很多空闲的区域,比如在执行一个“从内存取值”指令期间,ALU会闲置。所以一次性处理多条指令会更好。

3.多核处理器

前面的方法是优化1个指令流的吞吐量,另一种提高性能的方法是同时运行多个指令流,用多核处理器multi-core processors。意味着一个CPU芯片里有多个独立的处理单元,它们整合紧密,可以共享一些资源,比如缓存,使得多核可以合作运算。当多核不够用时,可以用多个CPU

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值