- 处理器是要好好复习了,毕竟考试重点
- 主要的内容感觉还是在指令方面,包括指令的格式、指令集的设计、指令流水线、指令级并行。
- 东西慢慢整理吧,如果一边在网上搜索的估计会找到很多资料,但是耗费的时间太多了
- 国内的教材虽然有一定的局限性,但是tm的要考试啊
- 我觉得需要考试的人不止我一个,,,,,,
处理器的组成与功能
先给一张处理器的概念图,未必很严谨,而且各种处理器的实现方式也不尽相同,但基本的原理其实都在图里可以体现
组成
- 大规模集成电路技术的发展,使得芯片的密集程度越来越高,处理器内部的各个结构甚至可以集成到一个半导体芯片上,体积那么小所以称为“微处理器”
- 微处理器外观上CPU有着密密麻麻的“针脚”,这些针脚就是用来数据传输的,一般一个针脚传送1位的数据,大概是根据有电没电来判断1或0?
- 内部是硅晶片,上面有数以百万计的晶体管。由晶体管就可以构成基本的门电路结构,进一步构成ALU等等基本的元件,再进一步就组成了CPU。
- 针脚
- 电源针脚VCC和接地针脚GND,除此之外的针脚都用于信号传输
- 数据总线DB(Data Bus)针脚,用于传输数据
- 地址总线AD(Address Bus)针脚,用于传输地址
- 也有通过总线复用技术,让数据和地址共用一套总线,也就是这些针脚既可以传输数据也可以传输地址,用时序信号来控制当前的是数据还是地址
- 控制总线CB(Control Bus)针脚,用于传输各种控制信号,常见的有
- 时钟信号CLK
- 复位Reset
- 总线请求HRQ
- 总线允许HLDA
- 读RD
- 写WR
- ……
- 根据冯诺依曼的模型,计算机的基本组成是
- CU控制单元、ALU运算器、存储器、输入单元、输出单元
- 在实现的时候通常把CU和ALU集成在一起构成PU(Processing Unit),PU也被称为“处理器”(Processo)
- 通常计算机只拥有一个PU,而PU又作为计算机的核心部件,所以又被称为CPU(Central Processing Unit)中央处理器
- PU由ALU、CU以及一些暂存单元寄存器Register组成
- ALU的内部有不同的电路可以完成不同的运算
- 加法器完成算数运算
- 逻辑运算器完成逻辑运算
- 移位器完成移位运算
- 求补器完成取反运算
- CU是控制单元,作用是接受指令并进行译码,生成微程序来控制CPU内的各个部件那个需要工作那个不需要工作,来完成指令的执行;其实就是一个能读懂指令并可以指挥处理器的各个部件来完成这条指令的指挥重心。基本组成如下
- 程序计数器PC,用于存放下一跳指令的地址
- 当前指令寄存器IR
- 指令译码器ID
- 控制信号发生器CSG
- 寄存器Register一般分为
- 存放数据的数据寄存器
- 存放地址的地址寄存器
- 存放各种标志的标志寄存器FR(Flag Register)
- 零标志ZF(Zero Flag),1表示运算结果为零
- 符号表示SF(Sign Flag),1表示运算结果为负
- 奇偶标志PF(Parity Flag),1表示运算结果中1的个数为偶
- 进位标志CF(Carry Flag)
- 溢出标志OF(Overflow Flag)
- ……
- 然而寄存器只能存储很少量的数据用来辅助处理器的运算,真正的数据都存在主存里,主存对应冯诺依曼模型中的存储器,所以处理器与主存之间的数据交换是十分频繁的。
- 处理器访问一次主存,无论是从主存读取数据还是往主存写入数据,都称为一次“访存”
- 访存地址寄存器MAR(Memory Access Register)
- 交换数据寄存器MDR(Memory Access Register),也有称为MBR(Memory Buffer Register),大概是觉得的MAR和MBR比较顺口吧…
- 读取:把主存地址写入MAR,启动读命令,目标数据就会从主存的相应位置传输到MDR中
- 写入:把主存地址写入MAR,把数据写入MDR,启动写命令,目标数据就会从MDR写入到主存的相应位置
- 注意MAR和MDR是“用户(汇编程序员)透明的寄存器”,也就是没有一条指令可以读写MAR。实际上你写一条从主存取数据的指令,就需要主存地址作为操作数,而这条指令的执行就需要把主存的地址写入MAR中才能完成访存操作。这都隐藏在指令中而不需要我们去操心。
基本功能
- 说起计算机来就是“算数”,好听点叫运算,运算的中心又在CPU。而运算其实也会涉及到很多方面,数据从哪来、怎么算、结果有哪些特性、算的二进制是数据还是地址、结果存到哪里去、运算有什么含义、如何通过信号来控制设备……
- 这一切都交给了指令的设计,即设计的指令需实现各种各样功能的要求,之后由指令编写的程序才可以真正的实现我们想要的功能
- 指令一般只涉及一些可以有硬件直接来完成的最基本最基础的功能,其余更复杂的功能都是由若干指令来实现的,当然也可以设计一条指令处理复杂的功能,这也就加大了硬件的复杂度和难度。由此就有两个指令集设计的方向,即精简指令集RISC和复杂指令集CISC。之后会讲
- 处理器的功能就是不断的执行一条条的指令,对于每一条指令,处理器一般分以下几步
- 取指令:根据PC的地址取出指令放入IR中,然后PC自加1
- 分析指令,也叫指令译码:将IR的操作码部分的取出送入指令译码器ID进行移码,译码的结果就是发出相应的控制信号来控制各个部件工作
- 取操作数,即这条指令所用到的数据 :根据操作数的地址来获取源操作数
- 处理操作数 :操作数送入运算器并根据ID的译码结果进行相应的运算
- 写入数据,即处理好的结果存放到相应的位置:将结果写入到指定的地址(可以是寄存器也可以是主存)
- 以上五步不一定每条指令都需要执行,比如一个简单的 mov ax,bx 只是将 bx寄存器 的数据送入 ax 而并不需要运算,也就不需要处理操作数
综合视角
- 处理器中的控制单元负责取指令并进行译码,以控制处理器中的各个部件来进行相应的操作,以完成指令的功能
- 寄存器可以暂存各种数据,以帮助指令实现功能,且寄存器的读取是最快的,要比每次都从主存取数据快得多
- 运算器负责进行运算,即处理相应的操作数
- MAR和MBR实现处理器与主存之间数据的交换,对汇编程序员透明,即我们不用管
- 其他的组件
- 中断单元负责中断的控制,中断是提高处理器效率的手段之一
- 再其他的我就不知道了,真正的处理器在设计的时候基本的这些器件都需要有,当然你有别的方法可以代替也不是不可以;而其他的器件也可以往上添加,以实现更多的功能。
处理器的指令集
什么是指令
- 简言之就是人为设计的一种特殊的语言和计算机交流,计算机可以理解这种语言,人们用指令来指挥计算机完成人们希望完成的工作
- 为什么这种语言计算机可以理解呢?因为这是一种“人为的设计”,粗糙的讲就是规定好了每一条指令下计算机应该做那些事,这样不就相当于理解了吗
- 计算机指令Instruction
- 实际上一条指令更像一个函数,有的指令需要传入操作数,有的指令不需要操作数,根据操作数的数值、种类等等这条指令有会有一些不同的动作
- 所有的指令的集合称为“指令集”(Instruction Set)或“指令集体系结构”(Instruction Set Architecture,ISA)。
- 一种处理器肯定是听不懂其他不同种处理器的指令,所以指令集的概念也对应着某种指令集,比如经典的8086处理器的指令集,通常作为汇编语言的入门
- 这也就是说,汇编语言在不同的处理器上是不同的
- 这样,指令集其实就是这台计算机的逻辑表示,简单说就是指令集指明了这台计算机所有的功能
- 指令功能直接反应了计算机的功能
- 如果一个计算机不支持浮点数运算,那么你即使按照浮点数的格式存储了也没用,因为ALU的运算的逻辑电路根本没法实现浮点数的运算
- 指令功能决定了计算机的可编程性
- 像乘除这种复杂的操作,如果有一条乘指令那么对于编程就很容易了,否则恐怕就需要用移位、加减来实现,此时最好就是作为子程序来实现了
- 指令包含的操作,直接决定(体现)了ALU的规模与组成
- 也好比乘除,如果直接用硬件实现了乘除法,也就是ALU中有一个乘法器、除法器,那么指令也就可以有一条指令来实现乘除运算;如果想用一条指令实现乘除法,那么ALU中也应当设有用来进行乘除运算的乘法器、除法器
- 指令功能直接反应了计算机的功能
指令的基本格式与功能
- 指令的格式通常为“指令操作码”(Operation Code)加“指令操作数”(Operand)
- 指令操作码是一个二进制的码点,不同的二进制也就是不同的01串就代表着不同的指令,对应着不同的功能。
- 所以01串的操作码又叫“机器码”,因为只有机器才能直接识别二进制,而我们虽然设计了“机器码”,但01串本身并不包含指令的意思,对于我们来讲还只是一串01而已,尽管我们可以通过对应关系来找到某串01的操作码是何种功能
- 而汇编语言这种指令对人来说更好理解,因为已经是英文字母了,而且通常是某个单词的简写,但机器是无法直接识别字母的。所以汇编语言还需要进一步被翻译成机器码
- 操作数就是这条指令所需要处理的数据,可以没有,也可以多个,这要看指令集是如何设计的
- 指令的格式主要涉及到
- 操作码的长度
- 地址制和寻址方式
- 指令的长度
- 操作码的长度
- 定长:简化译码的实现,但指令的数量受限较大
- 不定长:常用指令设计得比较短,不常用的设计的长,一般采用扩展操作码的方式来实现,比如
- 最短操作码为4位,则对只对0000至1110设置为指令
- 1111则表示,当前操作码不止前4位,若再扩展4位,则有更长一级的操作码分别是1111 0000 到 1111 1111
- 以此类推
- 地址制
- 即一条指令中要处理多少个操作数
- 对于不定长操作码,操作码越短,可处理的操作数越多,或者容纳单个操作数的位数越多(当作为地址也就意味着寻址范围越大)
- 即一条指令中要处理多少个操作数
- 寻址方式
- 同一条指令比如mov,机器要如何判断所给的操作数是代表地址还是立即数还是寄存器呢?
- 一般书写的时候都有格式的规定,比如立即数就是数值,地址就要放在”[]”中
- 编译的时候根据操作数的寻址方式的不同,即使是相同的指令比如都是mov,也会译成不同的二进制代码,从而区分数据的寻址方式
- 指令的长度
- 一般都是字节的整数倍,按位算的话就是8的整数倍,这样方便计算机处理。
- 且在设计的时候尽量避免跨字节的设计,比如一个操作数的前n位在位于一个字节的后n位,而操作数的后m位就位于下一个字节的前m位了,这样一个操作数跨了两个字节,硬件处理起来会比较麻烦
寻址方式
- 寻址分为指令寻址和数据寻址。首先要从内存中获取指令,之后再根据每条指令需要用到哪些操作数,再去考虑如何获取操作数
指令寻址
- 指令寻址比较简单
- 顺序寻址:通常一段程序由顺序存储的指令组成,一条指令和下一条指令的物理地址是连续的,那么只要在根据PC(程序计数器,保存下一条指令的地址)获取了下条指令后,PC要自加以指向下一条指令
- 跳跃寻址:通常一段程序又不完全是连续的,比如分支指令下就会跳过某些指令,调用子程序的话就需要转到子程序所在的物理地址,这都是根据跳转指令来修改PC的内容,使PC指向相应地址的指令来实现跳转。
- 数据寻址就是说,如何去获取指令所需要的数据,因为如果数据是存在主存那么就需要主存的地址,如果是存在寄存器那就需要指明是那个寄存器
- 立即数寻址:即我的操作数就是我要处理的数据,那么就不需要到别的地方去取了
- 直接寻址:即我的操作数是数据所在的主存地址,这就需要根据主存地址去访问主存才能得到数据,但这种方式使得给出的主存地址的长度收到指令长度的限制
- 间接寻址:即我的操作数是一个主存地址,这个地址对应的数据是另外一个地址,而此时这个地址下的数据才是我要处理的数据,类似于指针的概念,这样就摆脱了直接寻址的长度限制,因为我数据所在的主存地址是存在主存里面的
- 寄存器寻址:即我的操作数存在寄存器里面,也就是已经在CPU里面了,只不过还需要进一步确认是那个寄存器,所以需要对CPU里的寄存器进行编址,比如32个寄存器就需要5位的二进制来编址
- 寄存器间接寻址:即我的操作数的主存地址存在寄存器里,还是需要访存才能获取真正的数据,但是可以修改寄存器的内容,这样用相同的指令(还是那个寄存器嘛)就可以处理不同的数据(但是寄存器的内容变了)
- 基址寻址:涉及到“逻辑地址”和“物理地址”的概念
- 逻辑地址:即程序段/数据段都是从零开始编址的,以程序段为例,对于一行行的指令所组成的程序,每一条指令对应的地址都是相对于程序起始位置的距离,比如第一条指令的逻辑地址为0000,第二条指令逻辑地址为0001等等。即使是不同的两段程序,其程序的逻辑地址也都是从零开始
- 物理地址:是我的每一条指令存在主存里时,我这条指令在主存的存储位置(也就是相对于主存第一个存储单元的位置),对于不同的两段程序,其肯定是存储在主存的不同的位置,物理地址必然不同
- 那么我们要访存自然就需要的是主存地址也就是物理地址,通过逻辑地址的概念我们可以知道,以程序段为例,如果我们知道了程序段的起始物理地址,那么对于每条指令而言,其物理地址就等于起始地址加逻辑地址,起始地址也成为基地址,即基址,
- 通常我们用寄存器把基址存下来,即基址寄存器,在指令中只给出逻辑地址,执行的时候需要把逻辑地址和基址相加得出物理地址才能访问来获得数据。
- 优点就是数据的逻辑地址的计算很方便,对于编译器实现起来也要简单得多
- 变址寻址:通常对于数组、向量等数据结构我们习惯通过下标来访问,就像在C语言中数组名表示数组的首地址一样,我们在指令中就把数组名显式地给出,把下标存到一个特定的寄存器——变址寄存器中,指令执行的时候就把数组名所代表的数组首地址和变址寄存器的下标相加得到物理地址来获取数据。只需要改变变址寄存器的内容就可以轻易的遍历数组或实现随机访问
- 堆栈寻址:前提是处理器支持堆栈数据结构,即有一个堆栈是由处理器自身来维护的,并配有“堆栈指针寄存器SP”指向栈顶。堆栈的操作一般就是“PUSH A”表示把存储单元A的内容入栈,SP逻辑上增1;“POP A”则是将栈顶内容出栈到存储单元A中,SP逻辑上减1。
- 之所以说SP“逻辑”上的增减,是由于栈的实现由两种,不过一般都是基于主存实现的栈,即栈的内容是保存到主存的,只不过这部分的数据只能通过PUSH和POP才能访问到
- 递增堆栈,即入栈导致SP内容(地址)增加,栈底的地址最小
- 递减堆栈,即入栈导致SP内容(地址)减小,栈底的地址最大
- 堆栈寻址:前提是处理器支持堆栈数据结构,即有一个堆栈是由处理器自身来维护的,并配有“堆栈指针寄存器SP”指向栈顶。堆栈的操作一般就是“PUSH A”表示把存储单元A的内容入栈,SP逻辑上增1;“POP A”则是将栈顶内容出栈到存储单元A中,SP逻辑上减1。
指令集的设计
综合分析计算机的功能,指令集的设计一般要实现一下五大部分的功能
- 算数/逻辑/移位指令,简称算逻指令(难听的简称)
- 算数
- 定点加ADD、减SUB、乘MUL、除DIV
- 浮点加ADDF、减SUBF、乘MULF、除DIVF
- 自增1指令INC、自减1指令DEC
- 比较CMP
- ……
- 逻辑
- 与AND、或OR、非NOT、异或XOR
- 有的还有“位测试”、“位清除”、“位求反”
- ……
- 移位
- 算术移位
- 逻辑移位
- 循环移位
- ……
- 注意各种运算的结果会对标志寄存器的标志位产生影响,如溢出标志OF会在运算结果有溢出的情况下被置为1否则置为0,进位标志CF会在运算结果有进位产生的情况下置为1否则置为0,等等其他标志位,
- 通过这些标志位可以方便的判断运算的情况,比如零标志位ZF通常用于比较两个数是否相等,即让两数相减看结果是否为零,也就是看ZF是否为1
- 算数
- 数据传送指令,简称数传指令(有过之而无不及的难听)
- 一般传送指令,如mov,实现数据的复制,有些计算机采用LOAD/STORE风格的指令来实现数据的传送,这种风格会给指令集的设计带来别样的好处
- 堆栈传送指令,即PUSH和POP,用于维护堆栈
- 数据交换指令,即交换两个存储单元的数据,一般需要第三方的介入作为暂存,执行时间也会因此比较长,其实完全可以由以上两种指令来实现数据的交换,不过数据交换用的比较多,设计成为一条指令来实现显然是收益大于损失的
- 控制转移指令
- 跳转JUMP,直接改变PC的值实现跳转
- 分支指令BRANCH,根据条件来决定程序的走向,即类似“ if 语句1 else 语句2”的逻辑,是继续顺序执行(即语句1)还是要做跳转(语句2)。
- 条件往往是上一次运算的结果,也就是标志位
- 为0转移指令,不为零转移指令等等等等,指令内部其实就是根据标志位来判断是否要转移,比如为0转移指令就看标志位ZF是不是1这样
- 子程序调用指令 “CALL 子程序名”
- 第一步把PC的值保存到堆栈中,因为还要返回嘛
- 第二步根据子程序名来求得子程序的入口地址
- 第三步把入口地址写入PC,继续运行的话下条指令也就是从获取的子程序的指令了
- 子程序在结束的时候会有一个返回的指令,作用是从堆栈中取出栈顶(PC)的值恢复到PC中,这样也就是返回了
- 子程序内部如果有对堆栈的操作,则一定要保证子程序返回的时候栈顶的值是最初进入子程序前压入的PC的值,这样才能正确返回,否则程序就会崩溃掉
- 输入/输出指令
- 主要是为了完成主存与外设之前信息交换的操作
- 启动I/O设备
- 停止I/O设备
- 测试I/O设备
- 数据的I/O传输
- 主要是为了完成主存与外设之前信息交换的操作
- 处理器控制及调试指令
- 调试指令、停机指令、特权指令、各种置/清除标志位指令…
- 对于面向多用户系统的处理器,处理器的状态又被分为“内核态”和“用户态”,即操作系统内核提供一些功能让用户程序可以调用,用户程序若要使用这些功能则需要“访管指令”来把处理器交给操作系统来实现相应的功能
- 一方面防止用户程序进行非法的操作如修改系统文件,这类操作都是有操作系统维护;
- 另一方面利用操作系统提供的功能也给程序的编写带来了便利
- 此外还有空操作NOP,等待指令WAIT
CISC与RISC
- 复杂指令集计算机CISC(Complex Instruction Set Computer)
- 即在指令集的设计的时候不断添加新的功能复杂的指令,使得一条指令可以实现原先复杂的功能,同时这也对处理器的设计提出了更高的要求,硬件的复杂度越来越高,但对编译器的开发却十分有利。
- 至今只有Intel及其兼容CPU还在使用CISC
- 精简指令集计算机RISC(Reduced Instruction Set Computer)
- 即在指令集的设计的时候不断去除功能复杂的指令,复杂的功能通过最基础的指令来编写成子程序来实现,对硬件要求不高,但对编译器有很高的要求
- CISC中虽然有着众多的指令,但实际上一个典型程序用到的80%的指令,只占指令集的20%,这样众多功能复杂的指令并没有很高的使用频率,却不得不加大了硬件设计的复杂度,似乎有点得不偿失了
- 典型如Apple公司的Macintosh是基于RISC体系结构
- 进一步区别
- CISC由于指令数目的众多,必然是不等长指令集,处理器相应起来的速度就相对于 RISC的等长指令集 要慢
- RISC处理器制造工艺简单,成本低,次品率也低
- CISC则运行着Dos、Windows这种家喻户晓的操作系统,拥有大量的应用程序,庞大的市场,65%以上的软件厂商都在为CISC体系的PC服务,Microsoft就是其中之一
- 相对来说RISC则显得有些势力单薄
- 不过最优的方案往往是两种极端的融合,目前也正逐渐呈现这种趋势,一般内核是基于RISC,而遇到CISC的指令又可以将其分解成为RISC的指令来处理
流水线技术
流水线原理
- 十分类似工厂里的流水线生产,对于一条指令,我们至少可以划分为“取指令”、“分析指令”和“执行指令”三部分,那么指令的处理也就可以按流水线的方式来进行
- 当然这种重叠进行的要求是,每个小部分之间的资源不冲突,或者有足够的资源来分配
- 假如两个部分都需要用到ALU的话,而且若ALU只有一个,那么很显然这两部分是不可能同时进行的
- 流水线的方式实际上也就实现了指令的重叠
- 这就要去对于指令的划分,要同时执行的两个部分的时间就需要控制成相等的时间,这也就需要一个同步的问题,通常执行时间会比较长,即使指令i+1的分析部分和指令i+2的取指部分已经执行完了,但仍然需要等待指令i的执行部分结束后,才可以进一步进行
- 设一条完整的指令运行完毕所需要的时间为 T
- 则对于指令的每个部分来说就要花费的时间为 t = (1/3)*T
- 从流水线的角度来看,也就是每隔 t 时间就可以执行完一条指令
- 若不采用流水线,则需要每隔 3t = T 的时间才可以执行完一条指令
- 当然由于同步的需要,每部分的执行时间原本不同,比如取指需要时间为 n,分析需要 n,而执行通常花费时间比较长,设为 2n的话
- 若不采用流水线,则是每隔 4n 的时间完成一条指令
- 若采用流水线,由于同步的需求,取指和分析也需要花费 2n 的时间,但从流水线上来看,便是每隔 2n 的时间就完成一条指令,获得的效益很明显
- 当然由于同步的需要,每部分的执行时间原本不同,比如取指需要时间为 n,分析需要 n,而执行通常花费时间比较长,设为 2n的话
- 也就是说,流水线的效率与指令的细分相关
- 一个很显然的优化策略就是,进一步细分每条指令
- 如执行部分可进一步分为“取操作数”和“执行”两部分,那么相对来说就是降低了“最大花费时间”的部分的花费时间
- 若取操作数和执行分别只需要 n 的时间
- 流水线上就会每隔 n 的时间完成了一条指令
- 如执行部分可进一步分为“取操作数”和“执行”两部分,那么相对来说就是降低了“最大花费时间”的部分的花费时间
- 流水线的原理除了可以应用在指令上,也可以应用在运算上
- 如浮点数加法,运算过程自然的分为了“求阶差”、“对阶”、“尾数相加”、“规格化”四部分
- 这四部分可以分别由独立的部件完成,以保证不会冲突而可以并行
- 如此称为“浮点数加法流水线”
- 流水线也常用时空图来描述
流水线的分类
- 不太想讲、简单一说吧、没什么意思
- 了解基本原理的基础上、自然也可以进一步的有多种变化与实现形式
功能分类
- 单功能流水线:只为某种特定的功能而设计,只设计到这种功能所需要的部件,也只能对这种特定的操作进行流水线化处理
- 多功能流水线:即其涉及到众多的部件,对于某种功能而言,就只利用其中的某些部件来对其进行流水线处理,众多的部件也使得其可以满足多种功能的流水化
连接方式分类
- 静态流水线:即在某一时段内,只能按照一种功能的流水化操作进行。尽管可能这种功能只涉及到了部分的部件,而其余的部件仍可以满足另外一种功能的流水化,也必须等到前者退出流水线后,后者才能进去。也就是所谓的静态
- 动态流水线:即在同时一段内,如果两种功能设计的部件不重叠的话,那么就可以同时进行流水化的处理。但其控制变得极为复杂,这使得这种方式代价太大,因此大多数流水线都是静态流水线
其他
- 部件级流水线:也叫运算操作流水线,即对算术逻辑运算进行流水化,比如浮点加法
- 处理器级流水线:也叫指令流水线,也就是之前分析的对指令进行细分以流水化处理的方式
- 处理器间流水线:也叫宏流水线,即由多个处理器串联来流水化作业
- 标量流水和向量流水
- 线性流水和非线性流水
性能评价
- 以五个流水段为例
- 建立时间 Te:第一个任务从进入到完成的时间,经过 Te 后才能达到完全流水化
- Te = 5 * ? t
- 排空时间 Td:最后一个任务从进入(完成第一部分才叫进入)到完成的时间,经过 Td 后流水线才能排空,进入空闲状态
- Td = 4 * ? t
- 总时间T:就是总时间
- T= Te + (N-1) * ? t = Td + N * ? t = N * ? t + ( M - 1) * ? t
- 加速比Sp:串行处理的话所花费的时间与流水化处理所花费时间的比率,意味着我们的流水线技术可以提高的效率
- Sp = N * M * ? t / T = N * M * ? t / ( N * ? t + (M-1) * ? t )
- Sp = M / [ 1+(M-1)/N ]
- 吞吐量Tp:单位时间流水线完成的任务数量
- Tp = N / T = 1 / { ? t * [ 1+(M-1)/N ] }
- Tp = (1/? t) / [1 + (M-1)/N]
- 最大吞吐率Tp_max:即在完全流水化的状态下的吞吐率,此时每隔? t就有一个任务完成
- Tp_max = 1 / ? t
- 效率E:指流水线的设备利用率,从时空图上看。即在总时间T内,N个任务所占用的区域与总的区域的比值。
- 显然空出来的区域就是用在建立和排空上,因为在这两部分时间里流水线并不是所有的设备都在使用
- E = (M * N * ? t) / (M * T) = N / [ N+(M-1) ]
- E = Tp * ? t
指令间的相关
- 之前有提到,流水线的基础就是,细分指令的每个部分之间互相独立,互不相关
- 但是这么苛刻的条件显然是不可能达到的,指令的条数众多,实现的功能众多,很难保证所有的指令之间都不相关
- 最最基本的方法就是“暂停流水线”,即如果产生相关则暂停后续指令的执行,直到相关性消失之后再继续流水化作业。
- 当然这是我们最不喜欢的解决方案,因为这是从 “根本”上来解决,因为冲突是由于流水而引起,暂停流水也就消除了冲突,但也就意味着暂停期间流水是不起作用的,无法得到效率的提高
- 结构相关:即对硬件资源的需求冲突,可能是竞争同一个资源,也可能是资源不够分配。主要表现是访问主存,以取指的访存与其他访存冲突为例
- 交叉访问存储器、哈佛结构存储器、多端口存储器
- 从存储器本身上下手,解决冲突问题,具体可百度
- 处理器内设“先行指令预取缓冲队列”
- 即把指令先都取到一个缓冲队列里,之后的取指都从队列里去,也就不必访问主存,就避免了冲突
- 定长指令集、指令和数据在存储器中“对齐”存放
- 这都是方便了访存,以保证取指可以在一个存储周期内完成
- Load/Store 风格
- 这个风格还记得吗?在这种风格中,只有Load和Store指令可以访存,其余的都是针对寄存器而无须访存,这样就降低了冲突概率
- 交叉访问存储器、哈佛结构存储器、多端口存储器
- 数据相关:后一条指令需要用到前一条指令的结果,那么在流水线中就有可能产生无法重叠执行的情况,即我后一条指令需要取操作数,但因为前一条指令还没有运行完,结果还没算出来,这就十分尴尬了
- 通常可以暂停执行,或有硬件实现,或有编译器插入空操作指令来实现,保证指令取操作数的时候都能取到
- 另外也有“数据旁路”的方法,基本思想就是一旦ALU算出了结果,就通过一条专用的传输线直接送到后续指令处;从ALU的角度来看,就是从运算结果SUM直接回到了输入缓冲A或B中
- 控制相关:当流水线遇到分支指令的时候,,,,,,分支的结果是要选择下一条要执行那条指令,,,,,,既然分支结果还没出来,那么我怎么知道并行执行的下一条指令是不是正确的指令呢??尴尬尴尬
- 肯定也可以暂停知道分支结果出来知乎,再继续流水正确的指令呗
- 再就是猜喽,猜对最好,猜错的话就需要撤销之前的操作,并按照猜测结果去取正确的指令
- 转移成功就是需要跳转,不成功就是不需要跳转
- 不同的猜测逻辑也有影响,因为转移的成功与否的分布于实际的程序有关,而不完全是随机分布的,比如循环的时候,循环操作内会“转移成功”,而只有退出循环的时候才会“转移不成功”
- 再就是硬件加速,让分支指令的结果可以尽早生成
指令级并行
何为指令的并行
- 所谓指令级的并行,就是“如果指令之间不相关,那么在流水线中就可以并行执行”
- 流水线的思想应用到指令上就是指令级并行;不过流水线只是一种思想,不仅仅可以用在指令上,还可以用在运算上等等
- 指令级并行Instruction-level parallelism,ILP
指令相关的进一步分析
- 数据相关
- 定义比较狭窄,仅指“先写后读”,即前一条指令写入的内容后一条指令需要读取
- 名相关
- 称指令用到的寄存器或存储器为“名”
- “先读后写”:前一条指令需要读取某个“名”中的数据,后一条指令需要向此“名”中写入,则这两条指令的顺序必须保证
- “先写后写”:即两条指令都往同一个“名”中写入数据,其顺序也是不能改变的
- 称之为“名相关“,就是因为这种冲突是由于使用了相同的“名”,而实际上两条指令之间可以不用相同“名”而同样可以完成操作
- 如先读后写中,改变后一条指令的名,让它写入别的地方就可以了啊,再往后的指令如果需要后一条指令的数据的话,那它的读取的名也跟着更改就是了
- 控制相关
- 依旧是分支指令惹的祸
- 两条原则
- 与控制相关的指令不能移到分支之前:因为这些指令需要分支的结果来判断选择那条进行
- 与控制无关的指令不能移到分支之后:因为分支会导致某些指令不需要执行,而与它无关的指令本应当不受分支指令的约束的
如此一来,在考虑指令并行的时候,处理器的CPI(平均指令周期数)就等于理想的CPI加上各种相关导致的停顿
CPI流水线=CPI理想+CPI控制相关+CPI结构相关+CPI先写后读(数据相关)+CPI先读后写+CPI写后写
并行相关技术
根据公式,提高每一项都可以提高最终的CPI流水线
- 基本流水线调度
- 如数据旁路就可以解决部分的数据相关
- 基本的暂停方法确实可以避免相关,但同时也降低了并行性
- 循环展开
- 讲多次的循环展开成显示的指令形式,以开发循环体的并行性,减少暂停的时间
- 解决控制相关
- 寄存器换名
- 解决名相关
- 指令的动态调度
- 这个比较复杂,不过可以解决各种相关的停顿
- 前瞻
- 在未判断指令是否可以执行之前,就执行,允许指令乱序,但顺序必须确认,同时把前瞻执行的指令、结果都标记为“未确认”,只有真正确认过的结果才算最终的结果
- 多指令流出
- 提高理想的CPI
指令的动态调度算法
- 首先说静态调度,就是编译器确认并分离出相关的指令,然后进行指令的调度,来消除或减少处理器的空转
- 所谓动态调度,就是通过硬件重新安排指令的执行顺序,来调整相关的指令在执行时的关系,减少暂停的发生,并且可以处理编译时未发现的相关
原理
- 通常流水线内一条指令未处理完,则后续的相关指令会被阻塞,其后的所有指令也都无法进行
- 动态调度将指令分为“取指”“译码”“取操作数”“执行”几步
- 译码:检查是否存在结构相关
- 取操作数:只要没有数据相关,就可以读取
- 意味着允许指令的乱序执行
- 所有指令在“译码”阶段都属顺序的执行的,而在“取操作数”则允许乱序执行
- 基本的算法有
- 记分牌算法
- Tomasulo算法:结合记分牌与换名技术,关键概念“保留站”
- 本质都是通过记录的各个部件的信息、指令运行的状态等信息,与部件之间通信,来控制指令执行的每一步
- 因为允许了指令乱序执行,所以可以很大程度减少处理器的暂停
- 同时所有的状态都在严密监控之下,各种约束条件保证了指令执行的结果是正确无误的
- 乱七八糟的内容,,,算是勉勉强强看过一遍吧,然近期还有很多别的事要处理
时间: 2024-11-01 16:55:42