在解释什么是指令流水线之前,我们需要清楚指令的真实执行过程,一个计算机对于多条指令的执行有以下的几种方式:
(1)串行执行:传统的冯诺依曼机是把指令一条一条的执行的,这被称为串行执行的执行方式,它设计起来很简单,也很好控制,但是处理器一次只能处理一个指令,导致速度很慢。如图所示(我们假设每一执行的阶段时间都是t,并且指令有n条):
(2)重叠执行:既然一次执行一条慢,于是我们就会想到既然一个指令可以被分为多个阶段(取指译码和执行),而每一个阶段执行时所需要的硬件不一样,那么我们为什么不把指令的各个阶段拆开来执行呢?也就是同时并行的执行多个指令的不同阶段(这其实在前面已经分析过一次),这样可以大大地节省时间,这就是重叠执行的思想。但是根据重叠的方法,还可以再细分:
{1}一次重叠执行:前一条指令的执行阶段和后一条指令的取指阶段重叠:
第一次执行需要3t的时间,后面执行一次只需要2t。所以总的来说,减少了约3分之一的时间。但是缺点是,它需要额外的硬件来实现两个指令一同执行,开销更大(其实就是操作系统的临界区问题)。
{2}二次重叠: 这个不用多说了,更节约时间(同时可以执行三条指令),但是实现起来更加复杂:
=这种重叠执行指令的执行方式就是指令流水线!!! 实际上,指令流水线的执行不可能像图上这么流畅,因为不同指令的执行周期和取指周期还有译码阶段,它们的硬件是有可能发生冲突的,所以上面的是理想状态,现实中会比上面的理想状态实施起来慢(有冲突只能等),但是总是比串行执行要快的。
PS:这里只是把指令分成了3个阶段,但是指令其实可以分成许多阶段(你甚至可以规定一个节拍就一个阶段),然后就可以同时让多条指令并行执行。考试最常考5个阶段的指令,这个后面说。
这是一个考试常见的图,它是用于表现指令流程线一个常用的画法:
图中的横坐标是时间,纵坐标是执行到了什么阶段,纵坐标有几个就代表题目把执行过程分成了几个阶段。 图中的 I 1 , I 2 . . . I_1,I_2... I1,I2...等就是指令的序号。从图中还可以看出它的指令流水线采用了三次重叠的执行方式。时空图的重点是可以帮助我们计算指令流水线的性能,这需要先学习一下指令流水线的性能指标。
有三个指标:
(1)吞吐率(TP):单位时间内,流水线可以完成多少条指令。 设一共要执行n条指令,执行完需要 T k T_k Tk的时间,那么:
T P = n T k TP = \frac{n}{T_k} TP=Tkn
现在用这个理想状态下的时空图为例子:
T P = n ( k + n − 1 ) Δ t TP=\frac{n}{(k+n-1)\Delta t} TP=(k+n−1)Δtn如果我们的指令无限多也就是n趋近于无穷时: T P = 1 Δ t TP=\frac{1}{\Delta t} TP=Δt1。其中, Δ t \Delta t Δt是执行一个指令的阶段所需要的时间(常常就是一个时钟节拍,因为都探讨极限理想了)。这里其实还给出了,n-1次重叠执行,所需要的时间,正好是 ( k + n − 1 ) Δ t (k+n-1)\Delta t (k+n−1)Δt。
这里再引入一个装入时间和排空时间的概念,其实就是从第一条指令执行到它执行结束的部分还有最后一条指令执行开始到结束的部分,从图上看正好像是一点点装进去又一点点排空。
这两个时间段使得硬件被一点点全部启动,然后一点点全部关闭。
(2)加速比S:完成同样的一批任务,不使用指令流水线和使用指令流水线所需要的时间比值。 说白了,就是和冯诺伊曼的串行执行相比,效率提高了多少。其实,用上面的例子去算,串行执行需要 n k Δ t nk\Delta t nkΔt的时间,所以 S = n k k + n − 1 S=\frac{nk}{k+n-1} S=k+n−1nk。显然,S的值越大越好(n越大它就越大)。n趋近于无穷的时候,加速比为k,所以极限情况下(指令足够多),分几个阶段就快了几倍。
(3)效率E:流水线设备的利用率。 计算方法其实就是计算整个时空图实体的面积(正好可以拼接成一个长方形)比上整个时空图的面积(就是一个大长方形)。
其实从图上可以看出,高固定是k,长就是 ( k + n − 1 ) Δ t (k+n-1)\Delta t (k+n−1)Δt,所以可以得出效率公式:
E = k [ ( k + n − 1 ) − ( k − 1 ) ] Δ t k ( k − 1 ) Δ t = n k + n − 1 E=\frac{k[(k+n-1)-(k-1)]\Delta t}{k(k-1)\Delta t}=\frac{n}{k+n-1} E=k(k−1)Δtk[(k+n−1)−(k−1)]Δt=k+n−1n 因为k是常数,所以很显然,n足够多的时候,这个值可以趋近于1,也就是利用率到了百分百。
从三个性能指标来看,当指令足够多的时候,叠的越多越好。当然这是理想状态,即每一个指令的执行时间相同且运行结束时,下一条指令立即衔接。
前面把指令分为了三段,现在进一步的扩展,把指令扩展成五段,这其实也是最常用的分段方法。这五段如图所示:
(1)取指令阶段IF。(2)指令译码阶段ID。(3)指令的执行阶段EX。(4)访存阶段M。(5)把计算结果写回通用寄存器WB。 这种五段式的指令,是由世界上第一个RISC指令集MIPS提出的(AMR是后期提出的)。要注意的是,这五个阶段是必须的,也就是说,即便有的指令没有后面两个阶段,计算机也会认为它们在执行(因为如果要认真的区分,设计起来会很复杂)。现实中每一个阶段用的时间是不一致的,但是设计的时候,为了方便实现,通常会把每一个阶段的时间设置成一样的,也就是用最长的那个阶段的时间作为全部的总时间。 (其实,只要是RISC指令集,都是这样设计的,因为指令短,花费这点时间来简化设计没有什么)。所以每一条指令的机器周期数相同,长度也相同才能更好的实现指令流水线。 因为每一个阶段的时间不同,那么一开始就肯定会存在空闲的时候(比如M只需要70ns,但是时间需要100ns,那么前30ns空闲),但是我们却知道因为要并行的运行许多指令,所以前一个阶段产生的数据不能一直占用寄存器,所以就会在每一个阶段后面加一个缓冲寄存器(图中ID后面的A和B还有Imm,EX后面的空白还有上面的store),这个缓冲寄存器也被称为锁存器。锁存器的功能就是保留本流水段的执行结果给下一流水段使用。
其次,图上出现了好久没有见到的Cache,这里就可以对Cache进行补充:如图所示,其实取指阶段和访存阶段往往访问的是Cahe,不是主存(回顾第三章)。然后图上也指明了,我们的Cache可以分成两个部分,一部分专门用来存指令,叫做指令Cahce,另一部分则用于存数据,称为数据Cache。而且这两个Cache其实是分成两半的,每一个都是一个单独的元件,这就导致它们可以并行的运行。 如果Cache不命中,则就必须花费更多的时间去访问主存,因为很耗费时间,所以指令流水线会因此出现断流(全停下来等)。
最后,我们需要注意的是,图上ALU在计算前数据都到了寄存器里面(然后存到AB锁存器里面),这是因为RISC指令集的运算必须两个数都在寄存器中,不能像CISC那样一个在寄存器一个在主存。 AB就是专门用来存放两个运算数的,上面的Imm锁存器则是用于存放立即数的。 M阶段后面的最小的框其实是一个寄存器,因为运算结果也可能是存入寄存器的(最后也可能像图上那样回流到通用寄存器里)。
(1)结构相关(指令之间的资源冲突)。 其实就是操作系统的互斥问题。比如,两条指令一个是取指阶段,一个是执行阶段,它们如果Cache未命中的话,就需要同时访问主存,引发冲突。同时,也会出现多条指令争抢同一个寄存器的现象。解决方法有:
1.后一条指令先暂停一个周期再运行。2.资源重复配置:即把资源分到不同的存储体中。 比如前面说的,把指令Cache和数据Cache分开来单独的设计。第一种容易实现,第二种效率更高。
(2)数据相关(它是重点考点)(后一条指令的执行可能需要前一条指令执行结束之后才获得)。其实就是操作系统的同步。解决方法:1.一直等待,直到需要的指令执行完成为止,可以通过硬件的阻塞(stall)[也被称为插入气泡]和软件插入"NOP"实现,其实就是指插入1到几个周期的空指令来等待。2.转发机制(数据旁路技术): 这个技术要更复杂一些,比如我们要计算 a=1+2,b=a-1。按照方法1,第二条减法指令需要等待第一条指令完全执行完第五个周期才可以,但是其实第一条指令在执行完执行阶段就已经有a的值了,所以就连接一条线到第二条指令,在第一条指令执行出a的结果以后,第二条指令立即开始执行,这样就可以少等待2个周期,但是会麻烦一些。3.编译优化:就是先执行后面的,不涉及到前一条指令的指令,然后等前面指令结果出来再继续执行,这其实是编译原理的知识不用深究,感兴趣可以看看。
(3)控制冲突。控制冲突是因为转移指令或者其他可以改变PC的情况(比如中断),打断了原本的指令执行顺序造成的(因为指令流水线实现的基础就是顺序的执行指令)。关键的是,转移指令只有在执行到第五个阶段结束你才知道需要转移,而这个时候下面的四条指令都已经开始执行了!解决办法:1.提前预测是否会跳转,又分为简单预测和动态预测两种,简单预测就是看到jmp指令,译码器就认为会跳转或者不会跳转(通常是全认为会跳转),动态预测就是额外加个硬件,多判断一下会不会跳转(因为硬件很简单,预测的也不是那么准确,它的原理一般就是根据历史情况,如果前面跳转的多,那就预测会跳转,少就不会,其实就是一个计数器)****。虽然动态预测很简单,但是准确率确实比简单预测要高一些。2.预取转移成功和转移失败两个方向上的目标指令: 这需要额外的加两个指令寄存器什么的,原理就是同时把转移成功和失败的指令都并行运行,例如jmp指令的第一阶段结束以后运行jmp成功的第一条,jmp指令第二阶段结束以后就运行jmp失败的第一条,这样最后总是有的指令是可以继续运行的不需要移除全部。3.加快和提前形成条件码:和第二章学习的全加器是一样的,因为转移的条件不一定需要计算出来才知道,我们可以连上额外的硬件,提前就计算出是否需要转移(说的玄乎其乎的,其实就是在第三个阶段执行结果出来以后立即判断是否转移,不等指令走后面两个阶段了,其实和数据相关的转发机制也很类似)。4.提高转移方向的猜准率: 其实还是第一种方法里面的动态预测,只不过用更好的算法来预测,但是算法更复杂也意味着更大的计算开销。
(1)部件功能级流水线:就是对每一个部件(比如ALU算术逻辑单元)再进行细分,然后让各个部分并行的完成操作。举个例子: 在一次加法操作中,ALU需要完成求阶差,对阶,尾数相加和结果规格化四个过程,其中,不同的过程用到的子操作元件不同,所以可以并行的执行。ps:因为它们是在一个指令里面发生的,所以不是指令流水线,是部件流水线(计组常见的,研究好一套理论以后到处用)。
(2)处理机级流水线:就是上面介绍的指令流水线,把指令分成5段,然后并行执行。
(3)处理机间的流水线:如果有多个处理器(可以理解成多核),那么就可以实现更多的指令并行执行,比如前面说的互斥的指令。也可以实现第一个处理器完成取指阶段,然后递交到处理机间的公共存储器,交给第二个处理器去完成第二阶段…
(1)单功能流水线:只能实现单一功能的流水线。比如要搞个连续加法的时候,我么就可以搞一个加法流水线,快速执行。
(2)多功能流水线:就是可以同时搞多种功能的流水线,比如同时加法和乘法等。
(1)静态流水线:同一时间内,只能完成同一种功能(比如只能执行加法)。
(2)动态流水线:同一时间内,可以执行多种运算。其实和按照能完成的功能分没有太大区别,只不过一个强调干了什么,一个强调时间而已。
(1)线性流水线:执行完当前指令就下一条,中间不存在对下面指令的反馈信号。
(2)非线性流水线:执行结束或者过程中对其他指令有反馈信号(最上面的图中就是,执行完成的结果最终还能回到译码阶段的寄存器中)。包括某些功能段会数次通过流水线(比如乘法是多次加法来实现的)。
其实就是多核运行的模式:
如图所示,就是一次可以同时运行多条指令的同一个阶段,它的特点是不能随便改指令的顺序,同时还要充分考虑互斥和同步问题,对编译器的要求很高。 图中流水线速度快了3倍。
它不需要多核来支持,它的原理如图:
不需要等待上一条指令的一个阶段执行完,就可以执行下一条指令。也就是一个时钟周期内,一个功能部件被使用多次。但是对编译设计人员的要求再次增加。图上的流水线速度也快了三倍。
说实话,这个不太好理解。基本原理是这样的:一些指令的执行操作可能会占用不同的部件,那么计算机就会把这些指令给合并成一条大指令,然后执行到执行阶段就全部并行的执行,这样一次就可以执行完很多指令。 它也是由编译器来完成的,因为操作码和地址码都合并了,所以指令会很长很长,所以叫超长指令字。同时,它必须有多个处理器才能实现(因为要一起取数据,一起执行,那需要多个MDR和MAR)。
一共有五个:运算类指令,LOAD指令,STORE指令,条件转移指令和无条件转移指令。其中,只有取数指令LOAD和存数指令STORE才需要访问主存。
指令名称 | 汇编语言 | 功能 |
---|---|---|
加法指令(寄存器和寄存器) | ADD R s , R d R_s, R_d Rs,Rd | ( R s ) + ( R d ) → R d (R_s)+(R_d)\rightarrow R_d (Rs)+(Rd)→Rd |
加法指令(寄存器和立即数) | ADD #666 , R d ,R_d ,Rd | 666 + ( R d ) → R d 666+(R_d)\rightarrow R_d 666+(Rd)→Rd |
算术左移 | SHL R d R_d Rd | ( R d ) < < < 2 → R d (R_d)<<<2\rightarrow R_d (Rd)<<<2→Rd |
表格中的 R s R_s Rs和 R d R_d Rd分别是源寄存器和目的寄存器,同时,因为RISC指令集只能允许寄存器相加,只有寄存器间加法和立即数存寄存器(立即数也是存在指令寄存器里面的,不需要访存)。所以运算类的指令往往不需要访存阶段! 现在根据下面的图,分析一下它们各个阶段发生了什么:
(1)IF取指阶段:根据PC里面的指令,从指令Cache里面取出需要的指令,存到IF的锁存器里面。
(2)ID指令译码阶段:分析当前指令,并且根据当前的运算类型,把操作数取到AB锁存器或取到立即数锁存器里面。
(3)EX执行阶段:根据ID锁存器里面的值计算,然后把结果放入锁存器。
(4)M空段:因为运算的结果最终只会存到寄存器,所以这里不会涉及访存,会空闲一个节拍。
(5)WB写回阶段:这个阶段就把值写回到寄存器里面(仔细看上面的图最后有一条线连接到ALU后面的寄存器里面)。
LOAD指令是取数指令,它的汇编写法是:LOAD R d R_d Rd,888( R s R_s Rs),它的伪代码写作 (888+( R s R_s Rs)) → R d \rightarrow R_d →Rd。这其实是一种偏移寻址的写法,内存地址中真实数据的位置在基址寄存器+888的位置。它也被写作:LOAD R d R_d Rd,mem 也就是省去了偏移的过程,伪代码写作:(mem) → R d \rightarrow R_d →Rd。它的各个阶段执行过程如下 :
(1)IF取指阶段:根据PC里面的指令,从指令Cache里面取出需要的指令,存到IF的锁存器里面。
(2)ID指令译码阶段:将基址寄存器的值放入到锁存器A,偏移量放入立即数锁存器Imm。
(3)EX执行阶段:把两个锁存器的值相加,计算出有效的地址放到后面空白的锁存器里。
(4)M段:根据有效地址,取数,然后放入M的锁存器。
(5)WB写回阶段:把最终的值放入目的寄存器.
STORE是存数指令,所以和LOAD取数指令十分地相似。它的汇编写法是:STORE R s R_s Rs,888( R d R_d Rd),它的伪代码写作 R s → R_s \rightarrow Rs→(888+( R d R_d Rd))。正好就是取数指令反过来的写法。同样的,它也可以简写为:STORE R s , m e m R_s,mem Rs,mem 记作:STORE R s → ( m e m ) R_s\rightarrow (mem) Rs→(mem)。它每一段的执行过程和上面也很相似:
(1)IF取指阶段:根据PC里面的指令,从指令Cache里面取出需要的指令,存到IF的锁存器里面。
(2)ID指令译码阶段:将基址寄存器的值放入到锁存器A,偏移量放入立即数锁存器Imm,将要存的数据放入锁存器B中。
(3)EX执行阶段:把两个锁存器的值相加,计算出有效的地址放到后面空白的锁存器里,并且把要存的值放入到锁存器store(store和B是直接相连接的,所以可以直接存入)。
(4)M段:把对应的数据写入Data Cache。
(5)WB写回阶段:空段。
条件转移指令有很多,这里以beq为例(beq就是等于的时候跳转)。它的汇编格式和功能如下:
汇编格式 | 功能 |
---|---|
b e q R s , R t , # 偏移量 beq\quad R_s,R_t,\#偏移量 beqRs,Rt,#偏移量 | 当 ( R S ) = = ( R t ) (R_S)==(R_t) (RS)==(Rt)时(PC)+指令字长+(偏移量x指令字长) → \rightarrow →PC |
否则:(PC)+指令字长 → \rightarrow →PC |
你有可能已经忘记了前面的知识,现在回顾一下,PC每一次跳转的都是一条指令,所以每一次都会自动的加“1”,这个1就是一个指令的字长,然后偏移往往不是在当前的PC值上偏移,因为在你执行结束的时候,PC的值已经自动的加“1”,所以需要在已经加1的情况下再偏移n条指令。所以PC+1这个操作被描述为:(PC)+指令字长 → \rightarrow →PC。它每一个阶段发生的事情如下:
(1)IF取指阶段:根据PC里面的指令,从指令Cache里面取出需要的指令,存到IF的锁存器里面。
(2)ID指令译码阶段:将要比较的两个数字放入锁存器A和B,然后把偏移量放入Imm寄存器。
(3)EX执行阶段:通过运算来比较两个数(前面已经学习过如何比较)。
(4)WrPC段(M段):修改PC的值,把目标的PC值写回PC。它准确来说不是M,也不是WB,因为用到的硬件是不一样的(图上没有画出),但是它需要时间很短,所以就放到M的时间段执行,不用太纠结,就是说它属于M段也是可以的。
(5)WB写回阶段:空段。因为前4个已经改完PC了。
无条件转移指令就和条件转移指令类似,但是简单许多。它的汇编是:jmp #偏移量,记作:(PC)+指令字长+(偏移量x指令字长) → \rightarrow →PC。这就不用过多解释了。
它每一段发生的如下:
(1)IF取指阶段:根据PC里面的指令,从指令Cache里面取出需要的指令,存到IF的锁存器里面。
(2)ID指令译码阶段:只需要把偏移量放入Imm寄存器。
(3)WrPC(EX):直接根据偏移量进行WrPC,因为用不到ALU,就用它来取代执行阶段。
(4)M段:空段。
(5)WB写回阶段:空段。
虽然条件转移和无条件转移指令存在空段,但是它们可以进一步的解决冲突的发生。 如果按照前面说的,执行到最后才修改PC的值,那么就会出现更多的冲突,这种提前修改PC的值,就可以最大限度地去避免冲突的发生。但是有的课本确实是把WrPC段设计到最后的(也就是WB阶段),这样的好处是实现起来简单,所以考试的时候一定要看清楚题目。
前面反复提到了多处理器系统,但是一直没有明确说明多处理器系统到底是什么,这部分就来简单学习一下。因为展开讲内容很多,但是考试只会简单的考察基本概念,不会考大题,所以这里就来简单的说一下。
基于指令流的数量和数据流的数量,计算机的体系结构可以分为SISD,SIMD,MISD和MIMD四类。常规的单处理器属于SISD,而常规的多处理器属于MIMD。
(1)单指令流单数据流结构(SISD):
SISD是传统的串行计算机结构,这种计算机通常仅包含一个处理器和一个存储器,处理器在一段时间内仅执行一条指令,按指令流规定的顺序串行执行指令中的若干条指令,为了提高速度,有的SISD计算机采用指令流水线的方式。因此,SISD处理器有时候回设置多个功能部件,并采用多模块交叉方式组织存储器。我们前面学的一直都是SISD系统。
就是单核CPU。它一次只能处理一到两个数据(比如a=a+b,不能处理a=a+b+c,处理a+b+c需要两条指令)。它每一时刻只有一条指令执行,同时只能处理一到两个数据。它甚至连数据的并行处理都做不到。更别提线程并行。
(2)单指令流多数据流结构(SIMD):
SIMD是指一个指令流同时对多个数据流进行处理,一般称为数据级并行技术,这种结构的计算机通常由一个指令控制部件、多个处理单元组成、每一个处理单元虽然执行的是同一条指令,但是每一个单元都有自己的地址存储器,这样每一个单元都有不同的数据地址,因此,不同的处理单元执行同一条指令所处理的数据是不同的,一个顺序应用程序编译后,可能安置SISD组织并运行于串行硬件上,也可能按照SIMD组织并运行于并行硬件上。通常,SIMD在使用for循环处理数组时最有效,比如一条分别对16对数据进行运算的SIMD指令,如果在16个ALU中同时运算,则仅需一次运算时间就能完成运算。SIMD在使用case或switch语句时效率最低,因为此时每一个执行单元必须根据不同的数据执行不同的操作,然后只有一个结果是有效的。
它就可以用一条指令执行完多个数字的加法:a=a+b+c+d。早期的某些显卡,需要渲染多个像素点,用的也是SIMD架构。它每一时刻也只有一个指令在执行,但是能处理多个相同的数据。其实就是上面说的超长指令字结构! 它只能做到数据流并行,不能做到线程并行。
(3)多指令流单数据流(MISD)结构:
MISD是指同时执行多条指令,来处理同一个数据,实际上不存在这里的计算机。因为脱裤子放屁的操作不需要。当时只是正好想到而已。
(4)多指令流多数据流(MIMD):
这是目前最常用的处理器架构,比如因特尔的i5,i7等CPU用的就是MIMD的架构,你电脑和手机ipad里的大概率也是。因为有多个CPU,所以它的特点是可以并行地执行多条指令,同时处理多个不同的数据。 不仅支持线程级的并行(几个核处理同一条指令),还支持线程级以上的并行(一个核处理一个进程)。**它还有一个常用的名称,叫做多核处理器。**它还能分为以下两类:
[1]多处理器系统(一台计算机):全称是共享内存多处理器系统(SMP),各个处理器之间都可以通过LOAD,STORE指令来访问同一个主存,可以通过主存相互传递数据。 目前的手机电脑都是这种系统。
这里的主存指的其实包括了Cache,之前说过目前的Cache是被分级的,往往最高的两级L1和L2(速度最快的两级),是给每一个核都分配一个,然后由它们独享,剩下的L3内存更大也更慢才是共享的。图上画的LLC就是最低一级的Cache,负责几个核之间的通信。 当然,Cache不命中时也可以通过旁边的主存来交互。
[2]多计算机系统(多台计算机):云计算其实可以看作多计算机系统。它的特点就是由个计算机来完成多个指令和数据的处理,这些指令可以共同组成一个进程。它和上面的共享内存不同的是,每一个计算机都是独立的个体,有自己的内存,它们的内存数据无法通过LOAD和STORE来共享,只能通过互相发消息的方式来传递数据。
多计算机系统其实就是分布式计算系统,也被称为消息传递系统。它能实现MIMD的所有功能,且算力更强(因为电脑更多)。如果你发现一个程序的完成,处理各个指令的CPU的物理地址均相同,那么它就是MSP系统(因为说明是同一个计算机干的),如果各个指令的CPU物理地址不同,那么大概率就是分布式架构。因为多核处理器的核心都在一个CPU芯片上(你也可以理解成一块板子上有多个CPU,都是一个意思),所以又把每一个处理器称为一个片,把它称为片级多处理器。
MIMD可以做到线程和进程的并行,数据流并行更是不在话下。 需要注意的一点是,无论是是哪种系统,都是可以做到指令的并发执行的,只不过能并行执行指令的只有MIMD而已(MISD被开除了,哈哈哈)。
向量处理器只有一个功能,那就是进行向量相关的计算操作。不要小看这个操作,因为向量的运算和图形,渲染阴影等。向量操作的特点就是需要的计算量很大了,计算的形式也单一,所以就针对计算向量,专门推出了向量处理器。向量处理器的示意图如下:
里面的向量寄存器可以直接存一个大型的向量,指令的处理的数据单位均是以向量为单位的。它可以辅助数据的运算(因为从线代的思想来看,数据的计算都和矩阵息息相关)。因为矩阵涉及的数据是很大的(最常见的矩阵数据就是图像,一个图像有1080x720的像素),所以主存必须支持多端口读取,也就是可以同时并行读取许多条的数据(把数据多条同时读取就是矩阵)。这个了解即可。它可以看作是一个高级的SIMD,最常见的就是我们的显卡。
在操作系统里面已经学习过多线程的概念,但是不知道怎么实现多线程,现在我们就来学习如何真的用硬件实现多线程。这里要分清楚一个知识点,我们之前学习的指令流水线不是多线程的方法,一个指令不是一个线程,一个指令只是同一个线程里面的一小部分而已。所以如图所示的这种处理器,一次只能支持一个线程运行:
但是它支持线程的并发执行,每一次换线程的时候,只不过每一次换线程都会把当前线程的东西给入栈(参考函数调用栈),会浪费很多时间。
而支持硬件多线程的处理器如图:
不要把它当作多核处理器,这里线程的执行也是并发的。但是它在切换线程的时候,并不需要把当前线程的东西入栈,这是因为它有多组通用寄存器和PC,也就是当它需要切换线程的时候,只需要换一下这些寄存器就行了,不涉及访存,所以更快。但是硬件多线程又分为以下三种:
(1)细粒度多线程: 它在每一个时钟周期内仅发射一个线程的指令,也就是每一个时钟周期都换一次线程,它能做到同一个线程里面的指令并行执行,但是做不到多个线程并行执行。它的线程切换代价很低(因为不需要访存,也不需要额外操作)。
(2)粗粒度多线程:它在每一个时钟周期都发射同一个线程的指令,当线程的指令流水线发生阻塞(需要的资源被占用)的时候,才切换其他线程。它和上面的细粒度多线程一样,只能做到指令的并行执行,做不到线程的并行执行,且它切换线程的代价很大,因为指令流水线被阻塞的时候,有的指令可能已经被执行了许多,而它在切换以后不保存当前已经执行的微指令的结果,所以之后需要重新执行一遍当前的指令(其实挺复杂的,不需要深究)。
(3)同时多线程:一个核里面有两套甚至更多的ALU,可以支持多个线程并行执行。所以它可以在一个时钟周期内发出多个线程的指令,实现线程的并行执行。
配上王道的两个图:
能记住它们的区别即可,不会考大题的,只考察选择题。
本章就结束了,下一章我们将会更细致地探讨总线的相关知识。