我正在尝试编写一个简单的分支预测变量,该变量应该根据int中存储的历史记录输出TAKEN(1)或NOT_TAKEN(0)。但是,它始终输出TAKEN而不是动态更改预测。
#define PHT_CTR_MAX 3
#define PHT_CTR_INIT 2
class PREDICTOR{
private:
UINT32 counter;
public:
PREDICTOR(void);
bool GetPrediction(UINT64 PC);
void UpdatePredictor(UINT64 PC, OpType opType, bool resolveDir, bool predDir, UINT64 branchTarget);
};
PREDICTOR::PREDICTOR(void){
counter = PHT_CTR_INIT;
}
bool PREDICTOR::GetPrediction(UINT64 PC){
if(counter > (PHT_CTR_MAX/2)){
return TAKEN;
}else{
return NOT_TAKEN;
}
}
void PREDICTOR::UpdatePredictor(UINT64 PC, OpType opType, bool resolveDir, bool predDir, UINT64 branchTarget){
if(resolveDir == TAKEN){
SatIncrement(counter, PHT_CTR_MAX);
}else{
SatDecrement(counter);
}
}
PREDICTOR :: PREDICTOR用于“构建”预测变量(创建数组,设置初始值...),它从一开始就被调用。
PREDICTOR :: GetPrediction应该返回TAKEN(当计数器= 3或2时)或NOT_TAKEN(当计数器= 0或1时)。
在GetPrediction之后调用PREDICTOR :: UpdatePredictor。它通过resolveDir更新预测变量-resolveDir是分支的实际方向。 如果resolveDir = 1,它将使计数器饱和增加(饱和意味着它永远不会超过PHT_CTR_MAX)。 如果resolveDir = 0,则计数器递减。
尽管此预测变量确实很简单,但它不起作用。它抛出的结果与我刚刚执行GetPrediction {return TAKEN}完全相同,这显然是错误的。我的编码技能不是很好,所以我可能做错了一些事情-可能是在GetPrediction或UpdatePredictor函数中。
这是一个很好的预测变量示例,尽管这有点复杂:
#define PHT_CTR_MAX 3
#define PHT_CTR_INIT 2
#define HIST_LEN 17
class PREDICTOR{
private:
UINT32 ghr; // global history register
UINT32 *pht; // pattern history table
UINT32 historyLength; // history length
UINT32 numPhtEntries; // entries in pht
public:
PREDICTOR(void);
bool GetPrediction(UINT64 PC);
void UpdatePredictor(UINT64 PC, OpType opType, bool resolveDir, bool predDir, UINT64 branchTarget);
PREDICTOR::PREDICTOR(void){
historyLength = HIST_LEN;
ghr = 0;
numPhtEntries = (1<< HIST_LEN);
pht = new UINT32[numPhtEntries];
for(UINT32 ii=0; ii< numPhtEntries; ii++){
pht[ii]=PHT_CTR_INIT;
}
}
bool PREDICTOR::GetPrediction(UINT64 PC){
UINT32 phtIndex = (PC^ghr) % (numPhtEntries);
UINT32 phtCounter = pht[phtIndex];
if(phtCounter > (PHT_CTR_MAX/2)){
return TAKEN;
}
else{
return NOT_TAKEN;
}
}
void PREDICTOR::UpdatePredictor(UINT64 PC, OpType opType, bool resolveDir, bool predDir, UINT64 branchTarget){
UINT32 phtIndex = (PC^ghr) % (numPhtEntries);
UINT32 phtCounter = pht[phtIndex];
if(resolveDir == TAKEN){
pht[phtIndex] = SatIncrement(phtCounter, PHT_CTR_MAX);
}else{
pht[phtIndex] = SatDecrement(phtCounter);
}
// update the GHR
ghr = (ghr << 1);
if(resolveDir == TAKEN){
ghr++;
}
}
此预测器的工作方式与我的简单预测器相同,不同之处在于它使用计数器数组而不是单个计数器。调用GetPrediction时,将通过与PC(当前分支的地址)进行XOR运算的resolveDir(分支历史记录,全局历史记录寄存器或ghr)的后17位对数组进行索引。这将从数组中选择适当的计数器,然后将其用于进行预测。 UpdatePredictor以相同的方式工作,对数组建立索引,然后选择计数器。计数器使用来自resolveDir的信息更新。最后,全局历史记录缓冲区(ghr,分支历史记录,也可以根据需要对其进行命名)也已更新。
SatIncrement
和SatDecrement
函数的代码:
static inline UINT32 SatIncrement(UINT32 x, UINT32 max)
{
if(x<max) return x+1;
return x;
}
static inline UINT32 SatDecrement(UINT32 x)
{
if(x>0) return x-1;
return x;
}
感谢帮助。
答案 0 :(得分:2)
代码无法按预期运行的原因是SatIncrement
和SatDecrement
接受参数值并返回新值,然后必须将新值分配回应该被认为是递增/递减。
SatIncrement(counter, PHT_CTR_MAX);
将传递counter
的值,但不会修改counter
本身。不使用带有新值的返回值,因此该行实际上不执行任何操作。 SatDecrement(counter);
也是如此。
因此,分支预测变量永远不会更改状态,并且始终返回相同的预测。
按照其他代码示例修复它:
counter = SatIncrement(counter, PHT_CTR_MAX);
和
counter = SatDecrement(counter);
鉴于这是一种练习,您可能无法更改SatIncrement
和SatDecrement
,但是在实践中,可能会让这些函数按引用引用参数,以便它们可以直接修改传递的变量,避免在呼叫站点重复counter
:
static inline void SatIncrement(UINT32& x, UINT32 max)
{
if(x<max) x++;
}
如果选择了原始签名,则由于C ++ 17,可以在函数中添加[[nodiscard]]
属性,以使编译器在不使用返回值的情况下显示警告:
[[nodiscard]] static inline UINT32 SatIncrement(UINT32 x, UINT32 max)
{
if(x<max) return x+1;
return x;
}
它会在这里警告您,并使问题更加清楚。