体系结构复习1——指令级并行(循环展开和Tomasulo算法)

体系结构复习 CH5 指令级并行


5.1 指令级并行概念

5.1.1 指令级并行

指令级并行(ILP)指通过通过流水线等技术实现多条指令同时并行执行的并行技术

实现ILP主要的方法有:

  • 依靠硬件动态发现和开发并行
  • 依靠软件在编译时静态发现并行

5.1.2 指令间相关性

指令间的相关性限制了指令级的并行度,相关性主要分为(真)数据相关、名称相关和控制相关

(1)数据相关

指令i位于指令j的前面,下面两种情况下称指令j数据相关于指令i:

  • 指令i生成的结果可能会被指令j用到
  • 指令j数据相关于指令k,而指令k数据相关于指令j

数据相关在一定程度上限制了ILP,最常用客服相关性的方法是在不改变相关性的条件下通过指令调度消除数据冒险

(2)名称相关

当两条指令使用相同寄存器和存储器位置(称为名称),但与该名称相关的指令之间并没有数据流动,此时存在名称相关

主要分为以下两种情况(指令i位于指令j的前面):

  • 反相关:指令j对指令i读取的寄存器和存储器位置进行写操作时,发生反相关
  • 输出相关:指令i和指令j对相同寄存器和存储器位置进行写操作时,发生输出相关

名称相关并不是真正的数据相关,通过寄存器重命名技术来消除名称相关

(3)数据冒险

数据冒险是指指令间存在相关性并且这两条指令相聚非常接近,足以使执行期间的重叠改变相关操作数的访问顺序,数据冒险分成三类:

  • RAW写后读:j在i还没写入时就读取同一位置,会读取旧值
  • WAW写后写:j在i还没写入时就写入同一位置,会被i写入覆盖(存在于i指令执行时间较长,如浮点运算或乘法指令)
  • WAR读后写:j在i还没读入时就写入同一位置,i错误读取新值(一般先读后写的指令集不会出现,可能情况是j提前写而i在流水线后期才读的情况)
(4)控制相关

控制相关是指分支指令限定了指令i相对于其的执行顺序,和分支条件相关的指令必须先于分支指令执行,受控于分支指令的指令必须在分支指令后执行,不受控于分支指令的指令必须在分支指令前执行


5.2 软件方法的指令级并行——基本块内的指令级并行

基本块是指一段顺序执行的代码,除了入口处没有其他转入分支,除了出口处没有其他转出分支

考虑一下C语言代码:

for (i = 1; i <= 1000; i++) {
    x[i] = x[i] + s;
}

基本块对应的汇编程序为:

Loop:   LD      F0,0(R1)
        ADDD    F4,F0,F2
        SD      0(R1),F4
        DADDI   R1,R1,#-8
        BNEZ    R1,Loop

遵循以下指令延迟规定:

这里写图片描述

那么可以直接分析基本块汇编程序的指令周期延迟(共9个周期):

1Loop:  LD      F0,0(R1)
2       <stall>
3       ADDD    F4,F0,F2
4       <stall>
5       <stall>
6       SD      0(R1),F4
7       DADDI   R1,R1,#-8
8       <stall>
9       BNEZ    R1,Loop

5.2.1 静态调度

静态调度是指通过改变指令顺序而不改变指令间数据相关来改善指令延迟,把上述R1的递减改到前面并利用延迟槽技术(设置延迟槽为1)可以让上述基本快代码压缩到6个周期完成:

1Loop:  LD      F0,0(R1)
2       DADDI   R1,R1,#-8
3       ADDD    F4,F0,F2
4       <stall>
5       BNEZ    R1,Loop
6       SD      8(R1),F4

说明:

  • DADDI让R1递减提前,那么SD中存储位置是R1+8而不是R1
  • 延迟槽是无论分支是否成功都要执行的指令

5.2.2 循环展开

静态调度能够大幅提升基本快执行效率(50%),但是还有一个周期的停顿不能消除,那么由此引入另一种块内消除延迟方法——循环展开

循环展开是将循环中多个基本块展开成一个基本块从而填充stall间隙的方法

将上段基本块做4段展开,并做调度:

1Loop:  LD      F0,0(R1)
2       LD      F6,-8(R1)
3       LD      F10,-16(R1)
4       LD      F14,-24(R1)
5       ADDD    F4,F0,F2
6       ADDD    F8,F6,F2
7       ADDD    F12,F10,F2
8       ADDD    F16,F14,F2
9       SD      0(R1),F4
10      SD      -8(R1),F8
11      DADDI   R1,R1,#-32
12      SD      16(R1),F12
13      BNEZ    R1,Loop
14      SD      8(R1),F16

平均每个次循环仅需要14/4=3.5个cycle,性能大幅增加!

5.2.3 编译器角度来看代码调度

上述最优循环展开代码调度是我们手工完成的,能够完成高效的调度是因为我们知道R1代表枚举变量,并知道R1-32前的-16(R1)和16(R1)指向的同一内存区域,但编译器确定对存储器引用却不是那么容易,如其无法确定:

  • 同一次循环中,100(R4)和20(R6)是否相等
  • 不同次循环中,100(R4)和100(R4)是否相等

5.2.4 循环间相关

上面举例不同次循环间是不相关的,但如果出现下述情况:

for (i = 1; i <= 100; i++) {
    A[i] = A[i] + B[i];         //S1
    B[i+1] = C[i] + D[i];       //S2
}

S1和S2没有相关,但是S1依赖于前一次循环的B[i],这种循环常不能直接并行执行,需要修改代码:

A[1] = A[1] + B[1]; 
for (i = 1; i <= 99; i++) {
    B[i+1] = C[i] + D[i];           //S2
    A[i+1] = A[i+1] + B[i+1];       //S1
}
B[101] = C[100] + D[100];

5.3 硬件方法的指令级并行

之前所了解的静态调度技术存在困难,并且一旦出现无法消除的数据相关,那么流水线就会停顿,直到清除相关流水线才会继续流动

动态调度提出动态发射的思想,若流水线即将发生停顿,硬件会动态选择后续不会违反相关性的指令发射,这也就是常说的顺序发射、乱序执行、乱序完成

动态调度有许多优点:

  • 对一种流水线编译的代码可以在不同流水线上高效执行,而不需要针对不同微体系结构重新编译
  • 动态调度能克服编译器静态调度不能确定的相关性(上述难题)
  • 允许处理器容忍一些不确定/动态的延迟,如缓存缺失带来的延迟,静态调度是无论如何也不能做到这一点的

5.3.1 记分牌动态调度

记分牌算法把ID段分成两个步骤:

  • 发射:译码,并检测结构相关
  • 读操作数:等到没有数据相关时读入操作数

其主要思想为,在没有结构冲突时尽可能的发射指令,若某条指令停顿则选取后续指令中与流水线正在执行和停顿的指令发射

(1)记分牌四阶段
阶段内容
Issue发射如果当前指令所使用的功能部件空闲,并且没有其他活动的指令(执行和停顿)使用相同的目的寄存器(避免WAW),记分牌发射该指令到功能部件,并更新记分牌内部数据。如果有结构相关或WAW相关,则该指令的发射暂停,并且也不发射后继指令,直到相关解除。
Read读操作数如果先前已发射的正在运行的指令不对当前指令的源操作数寄存器进行写操作,或者一个正在工作的功能部件已经完成了对该寄存器的写操作,则该操作数有效。操作数有效时记分牌控制功能部件读操作数,准备执行。(避免RAW)
Execute执行接收到操作数后功能部件开始执行。当计算出结果后,它通知记分牌该条指令的执行结束,记分牌记录
Write写结果一旦记分牌得到功能部件执行完毕的信息后,记分牌检测WAR相关。如果没有WAR相关就写结果,否则暂停该条指令。
(2)记分牌硬件部件

1.Instruction status记录正在活动的指令处于四阶段中的哪一步

2.Function unit status记录功能部件(完成运算的单元)的状态,其中每个FU有9个记录参量:

  • Busy:该功能部件是否正在使用
  • Op:该功能部件当前完成的操作
  • Fi:目的寄存器编号
  • Fj,Fk:两个源寄存器编号
  • Qj,Qk:产生源操作数Fj,Fk的功能部件
  • Rj,Rk:标识Fj,Fk是否就绪的标志位

3.Register result status记录哪个FU对某个寄存器是否进行写操作(还未写入),若没有该域为空

(3)动态调度算法

有了上述三个部件,就可以完成四阶段中一些查看操作,如FU是否空闲、操作数是否就绪(能否执行)、是否存在WAW等

之所以该算法称为记分牌算法,是因为这三个部件就像公示的记分牌一样,流水线中各操作都需要去查看记分牌的状态并根据执行情况在记分牌中写入相应参数信息

将四阶段和记分牌控制用伪代码的形式给出,wait util是某指令向下阶段流水的必要条件,Book keeping是该流水段执行完毕后需要记录的信息:

StatusWait UntilBook Keeping
Issue!FU.busy && result[D] == nullFU.busy = true; FU.op = op; FU.Fi = ‘D’; FU.Fj = ‘S1’; FU.Fk = ‘S2’; Qj = result[S1]; Qk = result[S2]; Rj = (Qj == null ? true : false); Rk = (Qk == null ? true : false); Result[D] == ‘FU’(若是源操作数是立即数或R整数寄存器,对应Rjk直接是yes)
ReadRj && RkRj = true; Rk = true;
ExecuteFU completerecord complete cycle
Write任意其他FU的Fj和Fk不是FU.Fi(WAR)或者他们已经ready“通知”所有Fj和Fk是FU.Fi的其他FU该操作数ready,修改Rj/k为true; Result[FU.Fi] = null; FU.busy = false;

5.3.2 Tomasulo动态调度

另一种动态调度算法是Tomasulo动态调度算法,它和记分牌的区别主要有:

  • 控制和缓存在记分牌中是集中的,Tomasulo是分布在各部件中
  • Tomasulo的FU称做Reservation Station保留站(RS),RS中表示寄存器要么是寄存器值,要么是指向RS或Load Buffer(也可以看做特殊的RS)的指针;RS可以完成寄存器重命名,避免WAR、WAW,RS可以多于寄存器,实现更多编译器无法完成的优化
  • Register result status中记录的结果都是RS的名字,而不是寄存器
  • FU计算完毕后通过Common Data Bus(CDB)广播给所有其他RS,并修改Register result status中记录
  • Tomasulo可以跨越分支!不仅仅局限于基本快内部FP操作的乱序执行

Tomasulo受限于CDB的性能,一般采用高速CDB

(1)寄存器重命名

为什么寄存器重命名能够避免WAR和WAW?事例如下:

DIVD    F0,F2,F4
ADDD    F6,F0,F8
SD      F6,0(R1)
SUBD    F8,F10,F14
MULD    F6,F10,F8

存在下列三个名称相关:

  • SUBD的目的操作数是F8,ADDD源操作数是F8,存在WAR冒险
  • MULD的目的操作数是F6,SD源的操作数是F6,存在WAR冒险
  • MULD的目的操作数是F6,ADDD目的操作数是F6,存在WAW冒险

用T、S重命名寄存器,有:

DIVD    F0,F2,F4
ADDD    S,F0,F8
SD      S,0(R1)
SUBD    T,F10,F14
MULD    F6,F10,T

且后续F8都用T代替,那么有:

  • SUBD写入T可以先于ADDD读F8,WAR冒险消除
  • MULD写入F6可以在SD读入S之前,WAR冒险消除
  • MULD写入F6可以在ADDD写入S之前,WAW冒险消除
(2)部件结构

1.RS的结构和记分牌算法的FU相似,因为有了寄存器重命名,它省去了F和R两个标志位:

  • Busy:该RS是否正在使用
  • Op:该RS当前完成的操作
  • A:存放存储器地址,开始存立即数,计算出有效地址后存有效地址
  • Vj,Vk:源操作数的值
  • Qj,Qk:产生源操作数的RS

2.Register result status中存对某一寄存器写操作的RS名字

(3)三阶段
阶段内容
Issue发射如果对应RS空闲(无结构相关),则发射指令和操作数(RS重命名避免WAR和WAW)
Execute执行两操作数就绪后RS开始执行,若没准备好随时监听CDB以获取所需的操作数(避免RAW)
Write写结果CDB传送所有结果,并修改Register result status
(4)Tomasulo流水控制

Tomasulo动态调度算法的伪代码表示如下:

1.发射阶段:

// rs、rt为源操作数
// rd为目的操作数

void issue() {
    if op == FP operation {
        wait until: RS[r].busy == false; // r是和FP Operation对应的RS编号

        if RegisterStatus[rs].Qi != null {
            RS[r].Vj = null;
            RS[r].Qj = RegisterStatus[rs].Qi;
        } else {
            RS[r].Vj = Register[rs];
            RS[r].Qj = null;
        }

        if RegisterStatus[rs].Qk != null {
            RS[r].Vk = null;
            RS[r].Qk = RegisterStatus[rs].Qi;
        } else {
            RS[r].Vk = Register[rs];
            RS[r].Qk = null;
        }

        RS[r].busy == true;
        RegisterStatus[rd].Qi = r; 
    }

    if op == Load or Store {
        wait until: RS[r].busy == false; // Load Buffer和RS相同数据结构

        if RegisterStatus[rs].Qi != null {
            RS[r].Vj = null;
            RS[r].Qj = RegisterStatus[rs].Qi;
        } else {
            RS[r].Vj = Register[rs];
            RS[r].Qj = null;
        }

        RS[r].A = imm;
        RS[r].busy = true;

        if op == Load { // Load only
            RegisterStatus[rt].Qi = r;
        } else {        // Store only
            if (RegisterStatus[rd].Qi != null) {// 避免WAW
                RS[r].Vk = null;
                RS[r].Qk = RegisterStatus[rt].Qi;
            } else {
                RS[r].Vk = Register[rt];
                RS[r].Qk = null;
            }
        }
    }
}

2.执行阶段:

void execute() {
    if op == FP Operation {
        wait until: RS[r].Qj == null && RS[r].Qk == null

        compute result with Operand in Vj and Vk;
    }

    if op == Load or Store {
        wait until: RS[r].Qj = 0 && r is head of load-store queue(每次处理队头元素)

        RS[r].A = RS[r].Vj + RS[r].A;

        if op == Load {
            wait until: RS[r].A写入完成

            Read from Mem[RS[r].A]
        }
    }
}

3.写结果阶段:

void write() {
    if op == FP Operation {
        wait until: Execution of r is complete & CDB available

        for all x in RegisterStatus_Index_Set par-do { 
            // 硬件并行执行,模拟直接for循环串行模拟即可
            if RegisterStatus[x].Qi == r {
                Register[x] = result;
                RegisterStatus[x].Qi = null;
            }
        }

        for all x in RS_Index_Set par-do {
            if RS[x].Qj == r {
                RS[x].Vj = result;
                RS[x].Qj = null;
            }

            if RS[x].Qk == r {
                RS[x].Vk = result;
                RS[x].Qk = null;
            }
        }

        RS[r].busy = false;
    }

    if op == Store {
        wait until: Execution of r is complete & RS[r].Qk == null

        Mem[RS[r].A] = RS[r].Vk;
        RS[r].busy = false;
    }
}

5.3.3 Tomasulo处理循环

Tomasulo算法能够循环覆盖执行,关键点在于:

  • 寄存器重命名:不同循环中处理不同的物理存储位置,寄存器重命名将寄存器表示为动态的指针,增加了寄存器的个数
  • 整数部件先行:以便能够发射多个循环中操作
  • 26
    点赞
  • 93
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
Tomasulo算法实验报告 课程名称:高级计算机系统结构 学号: 姓名: 指导教师: 日期:2011年12月8日 Tomasulo算法实验报告全文共5页,当前为第1页。 Tomasulo算法实验报告全文共5页,当前为第1页。 实验目的 加深对指令集并行性及开发的理解。 加深对Tomasulo算法的理解。 掌握Tomulo算法在指令流出、执行、写结果各阶段对浮点操作指令以及load和store指令进行什么处理。 掌握采用了Tomasulo算法的浮点处理部件的结构。 掌握保留站的结构。 给定被执行代码片段,对于具体某个时钟周期,能够写出保留站、指令状态表以及浮点寄存器状态表内容的变化情况。 实验平台 Tomasulo算法模拟器。 实验内容 1. 假设浮点功能部件的延迟时间为加减法2个周期,乘法10个时钟周期,除法40个时钟周期,load部件2个时钟周期。运行程序的代码段如下: L.D F6, 24(R2) L.D F2, 12(R3) MUL.D F0, F2,F4 SUB.D F8,F6,F2 DIV.D F10,F0,F6 ADD.D F6,F8,F2 当指令MUL.D写结果时,保留站中内容如下表所示: 当指令MUL.D写结果时,load缓冲器中内容如下表所示: 当指令MUL.D写结果时,寄存器状态表中的内容如下表所示: 观察分析: 周期1:取出第一条指令L.D F6, 24(R2),地址偏移量24写入LOAD1,LOAD1名存入寄存器F6。 周期2:取出第二条指令L.D F2, 12(R3),地址偏移量12写入LOAD2,LOAD2名存入寄存器F2,同时第一条指令开始执行,LOAD1上写入绝对地址。 周期3:取出第三条指令MUL.D F0, F2,F4,第一条指令完成,第二条指令开始执行,LOAD2上写入绝对地址。保留站中存入待运算的操作数和操作。寄存器F0上QI写入保留站中待运算命令的名称。 Tomasulo算法实验报告全文共5页,当前为第2页。 周期4:取出第四条指令SUB.D F8,F6,F2,第二条指令执行完成,第一条指令写入结果M1到寄存器F6,保留站中存入第四条指令的待运算操作数和操作。LOAD1清空。 Tomasulo算法实验报告全文共5页,当前为第2页。 周期5:取出第五条指令DIV.D F10,F0,F6,第二条指令写结果M2到寄存器F2,LOAD2清空。保留站中存入第五条指令的待运算操作数和操作 周期6:取出第六条指令ADD.D F6,F8,F2,第三条和第四条指令开始执行,相关的操作数和操作符被存入保留站 周期7:第四条指令执行完成,保留站中的第三条指令继续执行。 周期8:第四条指令写结果M3到寄存器F8,保留站中存放第四条指令的位置清空,第三条指令继续执行。 周期9:第六条指令开始执行。第三条指令继续执行。 周期10:第六条指令执行完成,第三条指令继续执行。 周期11:第六条指令写结果M4到寄存器F6中,清空保留站中原来存放第六条指令的位置。第三条指令继续执行。 周期12~15:第三条指令继续执行,直到完成。 周期16:第三条指令写结果M5到寄存器F0,保留站中原来存放第三条指令的位置清空。 周期17~56:第五条指令开始执行,直到结束 周期57:第五条指令执行结束后写结果M6到F10,保留站中原来存放第五条指令的位置清空。 2. 对于与1相同的延迟时间和代码段 (1)在第3个时钟周期时,保留站内容如下表所示: 在第3个时钟周期时,load缓冲器内容如下表所示: 在第3个时钟周期时,寄存器状态表内容如下表所示: (2)步进5个时钟周期后,保留站内容如下表所示: Tomasulo算法实验报告全文共5页,当前为第3页。load缓冲器内容如下表所示: Tomasulo算法实验报告全文共5页,当前为第3页。 寄存器状态表中内容如下表所示: (3)再步进10个时钟周期后,保留站内容如下表所示: load缓冲器内容如下表所示: 寄存器状态表中内容如下表所示: 3. 浮点功能部件的延迟时间为加减法3个时钟周期,乘法8个时钟周期,除法40个时钟周期。load部件2个时钟周期。运行程序的代码段如下: L.D F12, 20(R5) L.D F2, 10(R6) MUL.D F6, F2,F10 SUB.D F0,F8,F2 DIV.D F10,F0,F12 ADD.D F8,F12,F2 在第3个时钟周期时,保留站内容如下表所示: load缓冲器内容如下表所示: 寄存器状态表中内容如下表所示: Tomasulo算法实验报告全文共5页,当前为第4页。 Tomasulo算法实验报告全文共5页,当前为第4页。 步进5个时钟周期后,保留站内容如下表所示: load缓冲器内容如下表所示: 寄存器状态表中内容如下表所示: 再步
1. 指令级并行:将一条指令分解成多个独立的操作,然后同时执行这些操作,以提高CPU的执行效率。 2. 循环展开:将循环中的多次迭代展开为一系列单独的指令,以减少循环的开销,提高程序的执行效率。 3. 指令调度:在执行指令时,通过优化指令的执行顺序和时间,尽可能地减少指令之间的等待时间,提高CPU的执行效率。 4. 数据相关:当一个指令需要依赖于另一个指令的计算结果时,就会产生数据相关。 5. 名相关:当一个指令需要访问另一个指令的目标地址时,就会产生名相关。 6. 反相关:当一个指令需要访问另一个指令的源操作数时,而该操作数正在被另一个指令计算时,就会产生反相关。 7. 输出相关:当一个指令的计算结果需要被另一个指令使用时,就会产生输出相关。 8. 控制相关:当一个指令的执行结果影响到另一个指令的执行流程时,就会产生控制相关。 9. 动态调度:在执行指令时,根据指令之间的相关性和优先级,动态地调整指令的执行顺序和时间,以提高CPU的执行效率。 10. 乱序流出:在执行指令时,将指令的结果按照计算完成的顺序而不是指令的顺序进行输出,以提高CPU的执行效率。 11. 乱序执行:在执行指令时,根据指令之间的相关性和优先级,动态地调整指令的执行顺序和时间,以提高CPU的执行效率。 12. 记分牌:一种用于实现动态调度的算法,通过分配和管理可用资源,实现指令的动态调度和执行。 13. Tomasulo算法:一种用于实现动态调度的算法,通过使用保留站和公共数据总线,实现指令的动态调度和执行。 14. 保留站:一种用于实现动态调度的硬件机制,用于暂存指令的操作数和状态,以实现指令的动态调度和执行。 15. 公共数据总线:一种用于实现动态调度的硬件机制,用于实现指令之间的数据传输和共享。 16. 分支预测缓冲:一种用于预测分支指令的执行结果的硬件机制,以减少分支指令对CPU性能的影响。 17. 分支目标缓冲:一种用于缓存分支指令的目标地址的硬件机制,以减少分支指令对CPU性能的影响。 18. 前瞻执行:一种用于提前执行指令的硬件机制,以减少指令之间的等待时间,提高CPU的执行效率。 19. 再定序缓冲:一种用于缓存乱序执行的指令的硬件机制,以保证指令的正确执行顺序。 20. 超标量:一种指令级并行的技术,将多条指令同时发射到多个执行单元,以提高CPU的执行效率。 21. 超流水:一种指令级并行的技术,将一条指令分解成多个阶段,每个阶段由一个或多个执行单元执行,以提高CPU的执行效率。 22. 超长指令字:一种指令级并行的技术,将多个指令打包成一个超长指令字,并同时发射到多个执行单元,以提高CPU的执行效率。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值