Branch Prediction
链接
https://gitee.com/startao1204/qrio_handout.git
链接: https://gitee.com/startao1204/qrio_handout.git
文章目录
前言
分支预测(Branch Prediction)在现代处理器中扮演着至关重要的角色。其主要目的是在执行指令流水线过程中预测分支指令的走向,从而提高处理器的执行效率。分支预测器根据历史信息和当前分支指令的特征来猜测下一个分支指令是否会被执行。通过减少分支错失(branch misprediction)导致的性能损失,分支预测技术可以显著提高指令流水线的效率。本文将介绍几种常见的分支预测器的实现,包括静态预测器、翻转预测器、相关预测器和GShare预测器。
StaticPredictor
静态预测器
不管实际的分支结果如何,它都根据预设的预测方向进行预测。
class StaticPredictor : public Predictor {
bool _predict;
public:
StaticPredictor(bool predict) : Predictor(), _predict(predict) {
if (predict) {
_name = "StaticPredictor-taken";
} else
_name = "StaticPredictor-not-taken";
}
void branch(bool taken, unsigned long pc) override {
/* bool _predict: */
/* true: predict the branch will always be taken */
/* false: predict the branch will always be not-taken */
_total++;
if (_predict != taken) {
_miss++;
}
}
};
在 branch 方法中,每次调用都增加总预测次数 _total,如果预测结果 _predict 与实际结果 taken 不同,则增加误预测次数 _miss。
_total++:这一行增加遇到的分支总数。大概_total是基类Predictor的一个成员,用于记录处理的分支总数。
if (_predict != taken) { _miss++; }:这一行检查预测值_predict是否与实际结果taken匹配。如果预测不正确,增加_miss计数器。_miss可能也是基类Predictor的成员,用于记录预测错误的次数。
FlipFlopPredictor
翻转预测器
使用一个简单的历史分支表来记录每个分支的预测结果。这个预测器根据分支的PC来索引表,并将实际结果保存到表中,以用于下次预测。
class FlipFlopPredictor : public Predictor {
unsigned char* _hbt;
int _pc_bit;
public:
FlipFlopPredictor(int pc_bit) : _pc_bit(pc_bit) {
_name = "FlipFlopPredictor (1-bit)*" + std::to_string(1 << _pc_bit);
int length = 1 << pc_bit;
_hbt = new unsigned char[length];
for (int i = 0; i < length; i++)
_hbt[i] = 0;
}
void branch(bool taken, unsigned long pc) override {
/* char _hbt[N]: history branch table */
/* pc_bit: number of PC bits used to index the table */
_total++;
unsigned int index = (pc >> 2) & ((1 << _pc_bit) - 1);
bool prediction = _hbt[index];
if (prediction != taken) {
_miss++;
}
// 更新预测器状态
_hbt[index] = taken;
}
};
通过 pc 的低 pc_bit 位计算索引,从表中获取预测结果 prediction,增加总预测次数 _total,比较预测结果 prediction 与实际结果 taken,如果不同,则增加误预测次数 _miss,更新表中的预测结果 _hbt[index] 为实际结果 taken。
_total++:这一行增加遇到的分支总数。_total大概是基类Predictor的一个成员,用于记录处理的分支总数。
unsigned int index = (pc >> 2) & ((1 << _pc_bit) - 1);:这一行计算用于索引历史分支表的索引。它将pc右移2位(通常是因为指令地址是按字对齐的),然后与掩码((1 << _pc_bit) - 1)进行与操作,以确保索引在表的范围内。
bool prediction = _hbt[index];:这一行获取当前索引位置的预测值。
if (prediction != taken) { _miss++; }:这一行检查预测值prediction是否与实际结果taken匹配。如果预测不正确,增加_miss计数器。_miss可能也是基类Predictor的成员,用于记录预测错误的次数。
_hbt[index] = taken;:这一行更新历史分支表中的预测值为实际结果taken。
CorrelatePredictor
相关预测器
使用一个更复杂的历史分支表,结合了PC和之前分支的历史信息。通过这种方式,它能够更好地捕捉到分支模式,从而提高预测准确率。
class CorrelatePredictor : public Predictor {
unsigned char* _hbt;
int _history;
int _pc_bit;
int _history_bit;
public:
CorrelatePredictor(int pc_bit, int history_bit)
: _history(0), _pc_bit(pc_bit), _history_bit(history_bit) {
_name = "CorrelatePredictor (" + std::to_string(_history_bit) + ",2)*" +
std::to_string(1 << _pc_bit);
int length = 1 << (pc_bit + _history_bit);
_hbt = new unsigned char[length];
for (int i = 0; i < length; i++)
_hbt[i] = 0;
}
void branch(bool taken, unsigned long pc) override {
/* (m,n)*T correlating predictor means a table with T n-bit predictors, */
/* m bits used to represent the taken/not-taken of the last m branches. */
/* Here, n is 2 that we use 2-bit predictors. */
/* */
/* char _hbt[N]: history branch table */
/* int _history: records taken/not-taken sequences of previous branches */
/* int _pc_bit: number of PC bits used to index the table */
/* int _history_bit: number of history bits used to index the table */
_total++;
unsigned int pc_index = (pc >> 2) & ((1 << _pc_bit) - 1);
unsigned int index = (pc_index << _history_bit) | (_history & ((1 << _history_bit) - 1));
bool prediction = _hbt[index] > 1;
if (prediction != taken) {
_miss++;
}
// 更新预测器状态
if (taken) {
if (_hbt[index] < 3) {
_hbt[index]++;
}
} else {
if (_hbt[index] > 0) {
_hbt[index]--;
}
}
// 更新历史记录
_history = ((_history << 1) | taken) & ((1 << _history_bit) - 1);
}
};
通过 pc 的低 pc_bit 位计算索引 pc_index,结合 pc_index 和历史的低 history_bit 位计算总索引 index,从表中获取预测结果 prediction,2位状态大于1表示预测为 taken,增加总预测次数 _total,比较预测结果 prediction 与实际结果 taken,如果不同,则增加误预测次数 _miss。
根据2位状态机更新表中的预测结果:如果实际结果是 taken,且状态小于3,则状态加1;如果实际结果是 not-taken,且状态大于0,则状态减1。然后更新历史记录 _history。
_total++:增加分支总数计数器。
unsigned int pc_index = (pc >> 2) & ((1 << _pc_bit) - 1):计算 PC 的索引。PC 右移 2 位(因为指令地址按字对齐),然后与掩码操作确保索引在范围内。
unsigned int index = (pc_index << _history_bit) | (_history & ((1 << _history_bit) - 1)):计算最终索引,通过将 PC 索引左移历史位数,再与历史记录进行或操作生成最终索引。
bool prediction = _hbt[index] > 1:根据表中的值进行预测,如果值大于 1,则预测分支会被采取。
if (prediction != taken) { _miss++; }:检查预测是否正确,如果不正确,增加错误计数器。
更新预测器状态:
if (taken) { if (_hbt[index] < 3) { _hbt[index]++; } }:如果分支被采取,并且表中的值小于 3,则增加表中的值。
else { if (_hbt[index] > 0) { _hbt[index]–; } }:如果分支未被采取,并且表中的值大于 0,则减少表中的值。
更新历史记录:
_history = ((_history << 1) | taken) & ((1 << _history_bit) - 1):左移历史记录,并将当前分支结果加入历史记录,再与掩码操作确保历史记录在范围内。
GSharePredictor
GShare预测器
使用PC和分支历史的异或值作为索引
class GSharePredictor : public Predictor {
unsigned char* _hbt;
int _history;
int _pc_bit;
public:
GSharePredictor(int pc_bit) : _history(0), _pc_bit(pc_bit) {
_name = "Gshare*" + std::to_string(1 << _pc_bit);
int length = 1 << pc_bit;
_hbt = new unsigned char[length];
for (int i = 0; i < length; i++)
_hbt[i] = 0;
}
void branch(bool taken, unsigned long pc) override {
/* index = pc-bit XOR history-bit */
_total++;
unsigned int pc_index = (pc >> 2) & ((1 << _pc_bit) - 1);
unsigned int index = pc_index ^ (_history & ((1 << _pc_bit) - 1));
bool prediction = _hbt[index] > 1;
if (prediction != taken) {
_miss++;
}
// 更新预测器状态
if (taken) {
if (_hbt[index] < 3) {
_hbt[index]++;
}
} else {
if (_hbt[index] > 0) {
_hbt[index]--;
}
}
// 更新历史记录
_history = ((_history << 1) | taken) & ((1 << _pc_bit) - 1);
}
};
通过 pc 和 _history 的异或值计算索引 index,从表中获取预测结果 prediction,2位状态大于1表示预测为 taken,增加总预测次数 _total。
比较预测结果 prediction 与实际结果 taken,如果不同,则增加误预测次数 _miss。
根据2位状态机更新表中的预测结果:如果实际结果是 taken,且状态小于3,则状态加1;如果实际结果是 not-taken,且状态大于0,则状态减1。更新历史记录 _history。
_total++:增加分支总数计数器。
unsigned int pc_index = (pc >> 2) & ((1 << _pc_bit) - 1):计算 PC 的索引。PC 右移 2 位(因为指令地址按字对齐),然后与掩码操作确保索引在范围内。
unsigned int index = pc_index ^ (_history & ((1 << _pc_bit) - 1)):计算最终索引,通过将 PC 索引和历史记录进行异或操作生成最终索引。
bool prediction = _hbt[index] > 1:根据表中的值进行预测,如果值大于 1,则预测分支会被采取。
if (prediction != taken) { _miss++; }:检查预测是否正确,如果不正确,增加错误计数器。
更新预测器状态:
if (taken) { if (_hbt[index] < 3) { _hbt[index]++; } }:如果分支被采取,并且表中的值小于 3,则增加表中的值。
else { if (_hbt[index] > 0) { _hbt[index]–; } }:如果分支未被采取,并且表中的值大于 0,则减少表中的值。
更新历史记录:
_history = ((_history << 1) | taken) & ((1 << _pc_bit) - 1):左移历史记录,并将当前分支结果加入历史记录,再与掩码操作确保历史记录在范围内。
总结
分支预测是提升处理器性能的重要手段。本文介绍了几种常见的分支预测器,每种预测器都有其独特的机制和适用场景:
静态预测器:通过预设的方向进行预测,简单但效果有限。
翻转预测器:利用历史分支表,根据PC的低位计算索引进行预测,适合简单的预测需求。
相关预测器:结合PC和历史信息,捕捉分支模式,提高预测准确率。
GShare预测器:使用PC和分支历史的异或值作为索引,在复杂分支模式下表现较好。
不同的分支预测器有不同的复杂度和预测效果,在实际应用中需要根据具体的需求和场景选择合适的预测器。通过不断优化和改进分支预测算法,可以进一步提升处理器的性能和效率。