【csapp实验5】分支预测器实验

一、题目

强大的分支预测器,是现代高性能处理器的关键技术。通过高准确度的分支预测,可以大大降低由于分支错误带来的性能损失。

本实验采用一个给定的分支预测器框架,需要同学们实现自己的分支预测器算法,尽力降低MISPRED_PER_1K_INST值(每一千条指令,预测错误的指令数)。 你只能修改predictor.c文件,其他文件请勿修改。(参考代码中,已经实现了一个经典的Gshare分支预测器,可供参考)

请参考Readme.txt文件获取编译信息。 【建议有能力的同学】实现TAGE分支预测器。TAGE分支预测器

【提交】请最后提交predictor.c文件、简要说明文件(采用的思路、分析、结论)、一个excel文件,描述课件要求的实验目标1~实验目标7的数据。

,

参考资料: 处理器结构--分支预测(Branch Prediction) 处理器结构--分支预测(Branch Prediction) - 简书 分支预测器 https://zh.wikipedia.org/wiki/%E5%88%86%E6%94%AF%E9%A0%90%E6%B8%AC%E5%99%A8 深入理解 CPU 的分支预测(Branch Prediction)模型 [转]深入理解 CPU 的分支预测(Branch Prediction)模型_分支预测器的重要性与条件分支指令执行频率有关-优快云博客文章浏览阅读1.6w次,点赞45次,收藏88次。目录背景问题的提出分析优化结论补充知识Pipeline分支预测器没有分支预测器会怎样?有分支预测期的pipeline常见的分支预测器引出 我写了一篇关于static key的文章,static key 主要是优化关于指令预取的性能,本想自己搞一篇什么是预取指令,但是这篇写的很好,直接转了,感谢作者的无私。 背景先来看段c++代码,我们..._分支预测器的重要性与条件分支指令执行频率有关https://blog.youkuaiyun.com/hanzefeng/article/details/82893317

ps: 这个实验 我觉着是挺难的其实……阿巴阿巴(其实就是他已经给了你一个Gshare分支预测器,前六个目标是在测试不同的参数和方案对你的分支预测的效果影响,最终根据这些,或者你有其他想法,在目标七里面实现你能做到的最好的分支预测器)

二、结果呈现

绿色是Gshare分支预测器的原始效果,黄色的是比Gshare差的效果,每一列是一个目标的实现(其中好像是目标三还是四来着???我改参数的时候忘记把前面改过的参数改回去,可能有些问题,不过前六组其实不重要……)

PER

PER

PER

PER

PER

PER

PER

PER

S-1

2.9176

3.3862

3.2712

2.7360

3.7467

1.3556

 2.1703

1.1672

S-2

11.3901

13.1526

9.2590

33.4588

8.5360

32.2583

11.3751

0.5612

S-3

3.7208

6.8658

3.5916

4.6309

3.5284

4.4361

3.7422

2.3002

S-4

5.1629

5.7700

4.8435

5.5694

4.5832

5.5958

5.1257

1.1349

S-24

0.0005

0.0009

0.0005

0.0005

0.0011

0.0005

0.0005

0.0005

S-25

0.0018

0.0035

0.0018

0.0017

0.0024

0.0018

0.0018

0.0018

S-27

0.4710

0.9239

0.4800

2.2689

0.7472

1.8205

0.0226

0.1404

S-28

0.0074

0.0145

0.0075

1.8285

0.0119

1.8285

0.0074

0.0083

S-30

7.0143

9.3236

5.7811

10.6007

4.5981

8.7485

4.8793

0.2364

L-1

0.5772

0.7187

0.4671

2.9978

0.5400

2.4923

0.5158

0.0888

L-2

1.3861

2.1498

1.3475

1.8343

1.2738

1.6427

1.3072

0.7670

L-3

7.7871

9.5228

7.6146

9.4502

6.9256

8.1653

7.7170

6.6121

L-4

0.0052

0.0097

0.0053

2.3095

0.0058

2.3084

0.0057

0.0065

其实根据前六个目标实现就可以总结出来,状态位数越高预测越精确,采集的地址位数越多越精确……等等等等,局部和全局预测结合起来更精准……根据这些去改你的目标七就好了!!!

三、原始代码源(放后面了,可以自行查看)

四、学姐的馈赠

T1  PHT_CTR_MAX=1

T2  HIST_LEN=20

T3  UpdatePredictor、GetPrediction函数:

UINT32 phtIndex=(PC&0x7FFFC)%(numPhtEntries);

UINT32 phtCounter=pht[phtIndex];

T4  PHT_CTR_MAX=7

T5  代码:主要是仿照模式历史表添加了一个局部历史表

///
  Copyright 2020 by mars.                                        //
///

#include <stdio.h>
#include <stdlib.h>

#include "common.h"

// 饱和计数器:加1
static inline UINT32 SatIncrement(UINT32 x, UINT32 max)
{
if (x<max) return x + 1;
return x;
}

// 饱和计数器:减1
static inline UINT32 SatDecrement(UINT32 x)
{
if (x>0) return x - 1;
return x;
}

// The state is defined for Gshare, change for your design
// Gshare分支预测器的状态信息,你需要根据自己的设计进行调整
UINT32 ghr;             // global history register  全局历史寄存器
UINT32 *pht;            // pattern history table    模式历史表
UINT32 historyLength;   // history length           历史长度
UINT32 numPhtEntries;   // entries in pht           PHT中的项数
UINT32 *jht;            // 局部历史表
UINT32 numJhtEntries;   // JHT中的项数

#define PHT_CTR_MAX  3
#define PHT_CTR_INIT  2

#define HIST_LEN   17   // 全局历史寄存器长度,取17位

#define TAKEN 'T'
#define NOT_TAKEN 'N'


void PREDICTOR_init(void)
{

historyLength = HIST_LEN;
ghr = 0;
numPhtEntries = (1 << HIST_LEN);    // 模式历史表,就有2^17项
numJhtEntries = (1 << (HIST_LEN - 3)); // 局部历史表,有2^14项
pht = (UINT32 *)malloc(numPhtEntries * sizeof(UINT32));
jht = (UINT32*)malloc(numJhtEntries * sizeof(UINT32));
    // 将模式历史表,全部初始化为PHT_CTR_INIT
for (UINT32 ii = 0; ii< numPhtEntries; ii++) {
pht[ii] = PHT_CTR_INIT;
}
for (UINT32 ii = 0; ii< numJhtEntries; ii++) {
jht[ii] = PHT_CTR_INIT;
}
}

// Gshare分支预测器
//将PC的低17位,与全局历史寄存器进行异或(加密),去索引PHT,得到对应的饱和状态
//如果该状态的值超过一半,则预测跳转
//如果该状态的值低于一半,则预测不跳转
char GetPrediction(UINT64 PC)
{
UINT32 qian = (PC >> 2) % numJhtEntries;
UINT32 tmp = (qian << 3) | jht[qian] % (1 << 3); //前14位来自PC,后三位来自LHT
UINT32 phtIndex = tmp % (numPhtEntries);

//UINT32 phtIndex = (PC^ghr) % (numPhtEntries);
UINT32 phtCounter = pht[phtIndex];

if (phtCounter > (PHT_CTR_MAX / 2)) {
return TAKEN;
}
else {
return NOT_TAKEN;
}
}

// Gshare分支预测器
//根据分支指令实际执行结果,来更新对应的饱和计数器
//如果结果为跳转,则对应的饱和计数器+1
//如果结果为不跳转,则对应的饱和计数器-1
// 更新全局历史寄存器:
//结果为跳转,将1移位到GHR的最低位
//结果为不跳转,将0移位到GHR的最低位
void  UpdatePredictor(UINT64 PC, OpType opType, char resolveDir, char predDir, UINT64 branchTarget)
{

    opType = opType;
    predDir = predDir;
    branchTarget = branchTarget;
    
UINT32 qian = (PC >> 2) % numJhtEntries;
UINT32 tmp = (qian << 3) | jht[qian] % (1 << 3); //前14位来自PC,后三位来自LHT
UINT32 phtIndex = tmp % (numPhtEntries);

//UINT32 phtIndex = (PC^ghr) % (numPhtEntries);
UINT32 phtCounter = pht[phtIndex];
// printf("PC=%016llx resolveDir=%c predDir=%c branchTarget=%016llx\n", PC, resolveDir, predDir, branchTarget);

if (resolveDir == TAKEN) {
pht[phtIndex] = SatIncrement(phtCounter, PHT_CTR_MAX);  // 如果结果为跳转,则对应的饱和计数器+1
}
else {
pht[phtIndex] = SatDecrement(phtCounter);  // 如果结果为跳转,则对应的饱和计数器+1
}

// update the GHR
ghr = (ghr << 1);

if (resolveDir == TAKEN) {
ghr = ghr | 0x1;
}
jht[qian] = jht[qian] << 1;
if (resolveDir == TAKEN) {
jht[qian] = jht[qian] | 0x1;
}
}

void PREDICTOR_free(void)
{
free(pht);
free(jht);
}

T6  UpdatePredictor、GetPrediction函数:

UINT32 phtIndex = tmp % (numPhtEntries); 

➡ UINT32 phtIndex = (tmp^ghr) % (numPhtEntries);

T7 引入opt做选择(因为全局和局部各有好处,所以引入一个新的预测表来判断用全局预测还是局部预测好)并启用新的hash函数    代码:

///
  Copyright 2020 by mars.                                        //
///


#include <stdio.h>
#include <stdlib.h>

#include "common.h"


// 饱和计数器:加1
static inline UINT32 SatIncrement(UINT32 x, UINT32 max) {
if (x < max) return x + 1;
return x;
}

// 饱和计数器:减1
static inline UINT32 SatDecrement(UINT32 x) {
if (x > 0) return x - 1;
return x;
}

// The state is defined for Gshare, change for your design
// Gshare分支预测器的状态信息,你需要根据自己的设计进行调整
UINT32 ghr;             // global history register  全局历史寄存器
UINT32 *pht;            // pattern history table    模式历史表
UINT32 historyLength;   // history length           历史长度
UINT32 numPhtEntries;   // entries in pht           PHT中的项数
UINT32 *jht;            // 局部历史表
UINT32 *opt;            // 预测使用哪个预测表会更好的预测表
UINT32 *lhr;
UINT32 ghr2;
UINT32 numJhtEntries;   // JHT中的项数
UINT32 numLhrEntries;   
UINT32 numOptEntries;
long long _base = 1e9 + 7;


#define PHT_CTR_MAX  15
#define OPT_CTR_MAX  7
#define JHT_CTR_MAX  3
#define PHT_CTR_INIT 2
#define OPT_CTR_INIT 2

#define HIST_LEN   24   // 全局历史寄存器长度,取17位
#define LOCAL_LEN 24
#define GHR2_LEN 24

#define TAKEN 'T'
#define NOT_TAKEN 'N'


void PREDICTOR_init(void) {

historyLength = HIST_LEN;
ghr = 0;
ghr2 = 0;
numPhtEntries = (1 << HIST_LEN);    // 模式历史表,就有2^17项
numJhtEntries = (1 << LOCAL_LEN);
numOptEntries = (1 << GHR2_LEN);
numLhrEntries = (1 << 24);


pht = (UINT32*)malloc(numPhtEntries * sizeof(UINT32));
jht = (UINT32*)malloc(numJhtEntries * sizeof(UINT32));
lhr = (UINT32*)malloc(numLhrEntries * sizeof(UINT32));
opt = (UINT32*)malloc(numOptEntries * sizeof(UINT32));


// 将模式历史表,全部初始化为PHT_CTR_INIT
for (UINT32 ii = 0; ii < numPhtEntries; ii++) pht[ii] = PHT_CTR_INIT;
for (UINT32 ii = 0; ii < numJhtEntries; ii++) jht[ii] = 2;
for (UINT32 ii = 0; ii < numOptEntries; ii++) opt[ii] = OPT_CTR_INIT;
for (UINT32 ii = 0; ii < numLhrEntries; ii++) lhr[ii] = 0x0f;
}

// Gshare分支预测器
// 将PC的低17位,与全局历史寄存器进行异或(加密),去索引PHT,得到对应的饱和状态
// 如果该状态的值超过一半,则预测跳转
// 如果该状态的值低于一半,则预测不跳转
char GetPrediction(UINT64 PC) {
UINT32 optIndex = (PC + ghr2 * _base) % numOptEntries; 
UINT32 choice = opt[optIndex];
if (choice > (OPT_CTR_MAX) / 2) {
UINT32 phtIndex = ((PC << 8) ^ ghr) % (numPhtEntries);
UINT32 phtCounter = pht[phtIndex];
if (phtCounter > (PHT_CTR_MAX / 2)) return TAKEN;
else return NOT_TAKEN;

}
else {
UINT32 lhrIndex = (PC ^ ghr) % numLhrEntries;
UINT32 ss = lhr[lhrIndex];
UINT32 tt = jht[((PC << 10) ^ ss) % numJhtEntries];
if (tt > (JHT_CTR_MAX) / 2) return TAKEN;
else return NOT_TAKEN;
}
}

// Gshare分支预测器
// 根据分支指令实际执行结果,来更新对应的饱和计数器
// 如果结果为跳转,则对应的饱和计数器+1
// 如果结果为不跳转,则对应的饱和计数器-1
// 更新全局历史寄存器:
// 结果为跳转,将1移位到GHR的最低位
// 结果为不跳转,将0移位到GHR的最低位
void  UpdatePredictor(UINT64 PC, OpType opType, char resolveDir, char predDir, UINT64 branchTarget) {
opType = opType;
predDir = predDir;
branchTarget = branchTarget;

UINT32 phtIndex = ((PC << 8) ^ ghr) % (numPhtEntries);
UINT32 phtCounter = pht[phtIndex];
// printf("PC=%016llx resolveDir=%c predDir=%c branchTarget=%016llx\n", PC, resolveDir, predDir, branchTarget);

UINT32 pht_pre = pht[phtIndex] > PHT_CTR_MAX / 2 == (resolveDir == TAKEN);
if (resolveDir == TAKEN) pht[phtIndex] = SatIncrement(phtCounter, PHT_CTR_MAX);  // 如果结果为跳转,则对应的饱和计数器+1
else pht[phtIndex] = SatDecrement(phtCounter);  // 如果结果为不跳转,则对应的饱和计数器-1

// update the GHR



UINT32 lhrIndex = (PC ^ ghr) % numLhrEntries;
UINT32 ss = lhr[lhrIndex];
UINT32 lhtIndex = ((PC << 10) ^ ss) % numJhtEntries;
UINT32 tt = jht[lhtIndex];

UINT32 lht_pre = (jht[lhtIndex] > JHT_CTR_MAX / 2) == (resolveDir == TAKEN);
if (resolveDir == TAKEN) jht[lhtIndex] = SatIncrement(tt, JHT_CTR_MAX);
else jht[lhtIndex] = SatDecrement(tt);

lhr[lhrIndex] = ((lhr[lhrIndex] << 1) | resolveDir == TAKEN);

UINT32 optIndex = (PC + ghr2 * _base) % numOptEntries;
UINT32 choice = opt[optIndex];
if (lht_pre != pht_pre) {
if (pht_pre) opt[optIndex] = SatIncrement(choice, OPT_CTR_MAX);
else opt[optIndex] = SatDecrement(choice);
}
ghr2 = (ghr2 << 1) | (lht_pre & pht_pre);
ghr = (ghr << 1);
if (resolveDir == TAKEN) ghr = ghr | 0x1;
}

void PREDICTOR_free(void) {
free(pht);
free(jht);
free(opt);
free(lhr);
}

### 关于处理器设计中的分支预测器 在现代计算机体系结构中,分支预测器是一个至关重要的组件,用于提高流水线效率并减少因条件跳转引起的性能损失。具体来说,在处理器执行过程中遇到条件语句时,分支预测器会尝试猜测程序是否会采取跳跃操作还是继续顺序执行下一条指令。 对于分支历史表(PHT),通常假定每个条目由一个两位饱和计数器有限状态机(FSM)构成[^2]。此 FSM 可能处于四种不同状态下:强取 (Strongly Taken, ST),表示有很大概率发生转向;弱取 (Weakly Taken, WT),意味着有一定几率转向;弱不取 (Weakly Not Taken, WN),暗示较小可能性转向;以及强不取 (Strongly Not Taken, SN),几乎不会转向。当新分支到来时,根据当前 PHT 条目的状态来决定是否应该提前加载目标地址的数据到缓存里等待验证。 值得注意的是,尽管上述描述基于一种理想化的模型,但在实际硬件实现中可能会更加复杂。例如,真实世界里的分支预测机制往往不仅限于此种简单形式,还可能引入额外的状态转换逻辑或者利用其它 CPU 数据结构的信息辅助决策过程。不过实验表明,即使是最基础版本的两比特饱和计数器也能很好地模拟出大多数商用微架构的行为特征。 ```c++ // C++ 伪代码展示了一个简易版的分支预测算法 class BranchPredictor { private: enum State { SN=0, WN, WT, ST }; vector<State> pht; // 假设大小固定 public: bool predict(int index){ switch(pht[index]){ case SN: return false; case WN: return false; case WT: return true; case ST: return true; } } void update(int index,bool taken){ if(taken && pht[index]<ST)pht[index]++; else if(!taken&&pht[index]>SN)pht[index]--; } }; ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值