一、题目
强大的分支预测器,是现代高性能处理器的关键技术。通过高准确度的分支预测,可以大大降低由于分支错误带来的性能损失。
本实验采用一个给定的分支预测器框架,需要同学们实现自己的分支预测器算法,尽力降低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);
}