超标量处理器概览
处理器性能和流水线
要加快处理器的执行速度的方法:
- 减少程序中指令数量:编译器、算法、扩展指令集
- 减少每条指令在处理器中执行所需要的时钟周期数(CPI)
- 减少处理器的周期时间
处理器的非流水时间是D,而流水线后的周期时间是$D/n + S$,其中n是流水线段数,S是流水线寄存器的延迟
流水线划分的条件:
- 每个阶段的时间近似相同
- 流水线每个阶段的操作都和其他流水线独立,但是不太可能
- 流水线每个阶段的操作都会被重复执行,但是访存操作可能不一样
如果每个流水线阶段的时间差异较大,如何处理?
- 合并:将许多小的流水线段合并成一个,保证每个段所占时间比较均衡,这种方法会导致时钟周期变大,适用于对性能要求不高的嵌入式处理器
- 拆分:将大的流水线拆成若干个小的流水段,可以降低时钟周期,获得较高的主频,但是会增大硬件消耗,适用于高性能处理器
超标量处理器
如果一个处理器每周期可以取出多于一条的指令送到流水线中执行,并且使用硬件来对指令进行调度,那么这个处理器就可以称作超标量处理器
顺序执行和乱序执行两种处理器的特点
下面是顺序执行的处理器流水线,每周期发射两条指令,由于要保证流水线的写回阶段是顺序执行的,因此每个功能单元FU都要执行相同的周期数
超标量处理器需要使用一个记分牌来记录每个逻辑寄存器的执行情况
- P:表示指令的结果还没有写回到逻辑寄存器
- F:一条指令在哪个FU(function unit)中执行
- result position:记录了一条指令到达了FU中的哪个阶段,每个周期都会进行右移
下面是乱序执行的流水线,在译码阶段新增加了寄存器重命名来解决WAW和WAR相关,需要增加一个物理寄存器堆PRF来对体系结构寄存器ARF进行重命名,由于存在异常和分支误预测,PRF的值不一定会写入ARF,因此PRF也叫做future file
其中SB是存储store指令没有提交之前的结果,store指令在写回阶段会将结果写入SB中,只有指令提交后才写入存储器,目的是为了解决异常的情况。因此load指令不仅需要从存储器中加载数据,还需要从SB中查找
重点关注乱序执行的超标量处理器,分为下面几个阶段:
- fetch:取指令,包括i-cache和分支预测器
- decode:产生控制信号
- register renaming:寄存器重命名,将逻辑寄存器映射到物理寄存器
- dispatch:重命名后的指令会按照顺序写到发射队列、ROB、和SB中,如果没有空闲的空间可以写入,就暂停流水线
- issue:发射,仲裁电路会从发射队列中挑出合适的指令进入FU中执行
- register file read:寄存器读取,被仲裁电路选中的指令需要从PRF中读取操作数,或者通过旁路网络,因为多发射,因此PRF的端口会比较多,因此读取速度较慢,放在单独一段
- execute:FU执行
- write back:将FU的计算结果写入PRF中,通过旁路网络将结果送到FU的输入端,由FU输入端的控制电路来决定最终需要的数据,旁路网络是影响速度的关键因素,现代处理器通常将FU划分成不同的组,布局布线紧挨在一起,组内的FU旁路可以一周期完成,组外的需要多个周期
- commit:ROB顺序提交指令,实现精确异常
Cache
Cache的一般设计
现代超标量处理器都是采用哈弗架构,L1 cache会分为i-cache和d-cache,采用sram实现
L2 cache是指令和数据共享的
3C定理:Compulsory、Capacity、Conflict
将选择字节的过程称之为数据对齐
cache访问一般是处理器中的关键路径
cache可以并行访问和串行访问(tag sram和data sram),并行访问的方式会有较低的时钟频率和较大的功耗(多路选择器),但是访问cache的时钟周期少了一拍;串行访问的功耗较低,可以使不访问的data sram使能信号置为无效,而且时钟频率较高,在超标量处理器中使用多,并不会增加的时钟周期而降低性能
在一般的RISC处理器中,i-cache都是不允许直接写入的,如果要修改i-cache,需要将指令作为数据写入d-cache中,再写入共享cache中,将i-cache中的内容置为无效,当处理器再次执行就会使用修改后的指令了
向cache写,分为两种情况:
- 写命中:写直达、写回
- 写缺失:写不分配、写分配
写分配是将下级存储器的块取出来放入cache中,然后再写cache;写不分配是直接写入下级存储器中。通常写直达和写不分配,写回和写分配一起使用
伪LRU算法,为每个cache line设置一个年龄部分,下面是一个八路组相联的例子,使用一个$\log2^8=3$位的年龄位
提高Cache的性能
写缓存
L2 cache或物理内存,一般只有一个读写端口,这要求L1cache缺失的操作是串行完成的,需要先将脏状态的cache块写回后才能读取缺失的数据,可以采用写缓存来隐藏延迟:
- 脏状态的cache块会先放到写缓存,然后就可以从下级存储器中读取cache块了
- 写缓存会等待下级存储器空闲时再择机写入
- 当L1 cache缺失时不仅需要在下级存储器中查找,还需要在写缓存中查找,写缓存中的数据是最新的
流水线
对于读d-cache可以同时读取tag sram和data sram,但是写就只能串行操作,先读取tag,再写data
下图是对cache写操作的流水线,插入了几个流水线寄存器,同时注意中间有一个比较器,用于解决store-load问题,可能下一条load读取的数据是store写入的数据,在流水线寄存器中,因此需要比较地址
多级结构
使用多级结构来获得大容量和快速的cache
在一般的处理器中,L2 cache会使用写回的方式,但是L1 cache可以使用写直达,因为延迟低
Victim Cache
从cache中换出的块可能马上又要被使用,因此可以使用一个cache来保存最近被踢出的块,这个cache叫做Victim Cache。下面的例子是保存L1 cache最近被换出的块,相当于增加了L1 cache中所有set的way的个数
一般情况下,victim cache和cache是互斥的关系
当L1 cache miss,但在vc中命中,就会从vc中返回块,同时将vc的数据写回到cache中,而cache中被替换的数据又会写道vc中(也会写到L2),相当于交换了数据
Filter Cache
当数据第一次被使用时并不会马上放入cache,而是放入fc中,数据再次被使用后才会放入cache中,这样可以防止那些偶然被使用的数据占据cache
预取
预取分为硬件预取和软件预取
硬件预取
对于i-cache适合使用硬件预取,因为大部分是顺序读取的
新增加个stream buffer,预取的数据会放到stream buffer中。当L1 i-cache发生miss时,除了读取需要的块,还会将下一个块读取出来放入stream buffer中;如果在i-cache中发生缺失,但是在stream buffer中找到了数据,则从stream buffer中返回数据,并将stream buffer中的块搬入i-cache中,同时从L2 cache中读取下一个数据块放到stream buffer
这种方法可能会浪费总线带宽,比如分支指令,导致无用功预取,可以运行时调整步长
软件预取
这种方法要求cache是非阻塞结构的
1 | for (i = 0; i < N; i++) { |
在实现了虚拟存储器的系统中,预取指令可能会引起一些异常,比如page fault,此时需要抛弃掉预取指令,变成空指令
多端口Cache
一般是使用bank的方法,每个bank只有一个端口,可能会存在bank冲突,解决方法是采用更多的bank
超标量处理器的取指令
超标量需要一次取出n条指令,此时需要将i-cache块数据部分的大小设置为n个字,但是只适用于n字对齐的情况,更多的情况是下面这种情况
解决方法是使cache块数据部分的大小大于n,再使用一个缓存将取出的多余指令缓存起来
如果一个cache块8个字,那么就需要8个32位的sram来实现这个cache,但是可以使用一种方法来使sram数量减半,如下图所示,一个cache块中包含八个字,但是只需要4个sram,每次取出四条指令
重排序逻辑电路的图,可以看到,根据取四条指令的第一条指令的地址来取出每条指令,非常巧妙!但是也有问题,如果取值地址指向最后三个字中的任意一个,则一次也取不出4条指令,因此重排序逻辑需要加入指示每条指令是否有效的标志
对于分支预测的情况,如果一条指令被预测执行,那么指令组中位于它后面的指令就不应该进入到后续的流水线
虚拟存储器
负责地址转换的单元一般称之为MMU
页表的结构是不同于cache的,是直接使用VPN(virtual page number)部分来寻址,不需要使用tag
当一个进程进行状态保存时,只需要保存PTR(页表基址寄存器)即可
单级页表可能会导致进程的页表占用的物理内存空间过大,因此采用多级页表
多级页表的好处:
- 不需要一下把整个线性页表都放入物理内存中,而是根据需要逐步放入这些子页表
- 子页表不需要占用连续的物理内存空间,提高了物理内存的利用效率
- 不存在的L1 PTE(页表项)不需要分配二级页表,只需要给已分配的L1 PTE分配一个完整的二级页表
pte中还需存在一个cacheable位,代表是否可以被cache所缓存,如果为1,则转换得到的物理地址先访问cache;如果为0,则直接访问外设或物理内存
- 如果是对外设的读写,则地址和数据是不会被cache所缓存的,因此需要给pte增加一个cacheable位
TLB
现代处理器很多都采用两级TLB,第一级TLB采用哈弗架构,一个指令TLB,一个数据TLB
在TLB中,除了使用位和脏位之外,其他的位在TLB中是不会变的,因此采用写回的TLB项在被替换时只需将这两位写入页表中,其余部分不用写回
TLB缺失的处理方式:
- 软件处理:时间包括异常处理程序的执行时间和异常处理程序退出后将流水线恢复到TLB缺失发生之前的状态的时间
- 硬件处理:硬件处理时只需暂停流水线,等待硬件处理完后可以继续执行,时间比软件处理短