[处理器芯片]-4 超标量CPU实现之分支预测

分支预测通过预测程序中分支指令(如条件跳转、循环、函数调用等)的执行路径,从而减少因分支指令带来的流水线停顿和性能下降,用于提高指令流水线的效率,是处理器非常关键的一项技术。

1 设计原理

分支指令:分支指令决定了程序的执行路径,当执行条件满足时跳转到目标地址,否则继续顺序执行。常见的分支指令有条件跳转(如 if-else 语句)、循环(如 for 和 while 语句)以及函数调用和返回。

流水线停顿:当处理器遇到分支指令时,如果不能提前确定跳转路径,就需要等待分支条件的计算结果,从而导致流水线停顿。

分支预测:分支预测通过猜测分支指令的执行路径(跳转方向和跳转目标),使处理器能够继续提前加载和执行指令,而不必等待分支条件的计算结果,从而提高指令级并行度和处理器性能。

2 预测分类

分支预测器通常按照预测的内容来分类,主要包括跳转方向预测和跳转目标预测。

1)跳转方向预测

跳转方向预测的主要目标是预测条件分支指令是否会跳转,即分支是否成立,大多数现代处理器的分支预测器集中在这一方面,又可分为静态预测器和动态预测器两种。

静态预测器

静态预测使用固定的规则预测分支跳转方向,如“总是跳转”或“总是不跳转”,不依赖运行时的分支行为,而是根据预定义的规则进行预测。

固定方向预测:总是跳转,假设所有分支指令都会跳转;总是不跳转,假设所有分支指令都不会跳转。这种方法在一些情况下效果较好,但总体预测准确性不高。

基于编译器的预测:编译器根据代码的上下文和结构,对分支进行预测,编译器可能使用启发式规则(如循环中的分支多为跳转)或静态分析(如频繁执行的路径)。

动态预测器

动态预测使用运行时的分支历史信息进行预测。

单比特预测:使用一个比特记录分支的上一次行为(跳转或不跳转),如果上一次分支跳转,则预测这次也跳转,反之亦然。

双比特预测:使用两个比特记录分支的历史行为状态(如强跳转、弱跳转、弱不跳转、强不跳转)。这种方法比单比特预测更稳定,减少了因为一次错误预测导致的连续错误预测。

全局历史预测:使用全局历史寄存器记录最近几次分支的结果,并根据全局历史进行预测。

局部历史预测:使用每个分支的局部历史记录进行预测,针对特定分支指令的行为进行更准确的预测。

混合预测:结合多种预测方法,综合使用全局历史和局部历史进行预测,并通过选择器选择最合适的预测器。

2)跳转目标预测

跳转目标预测,预测条件分支或间接跳转指令的目标地址,减少因跳转目标未确定而导致的流水线停顿。

分支目标缓冲器(Branch Target Buffer,BTB):缓存最近的分支指令和对应的目标地址,根据当前分支指令的地址快速查找目标地址。

返回地址栈(Return Address Stack,RAS):专门用于预测函数调用和返回指令的目标地址,通过维护一个调用/返回堆栈来预测返回地址。

间接跳转预测器:用于预测间接跳转指令(如函数指针调用或虚函数调用)的目标地址,可以使用类似于BTB的方法但更复杂。

3 分支预测器实例

TAGE预测器

TAGE(TAgged GEometric)是一种先进的分支预测器,专为现代高性能处理器设计:利用多个预测表,每个表使用不同长度的全局历史进行索引,并且每个条目都有一个标签,用于验证该条目是否适用于当前分支;通过选择最匹配的条目来进行预测,从而提高预测的准确性。

主要组件

基础预测器:通常是一个简单的两位饱和计数器数组,使用全局历史的一部分来进行索引。

多个标签预测表:每个表具有不同的历史长度,条目由索引和标签组成,标签用于验证条目是否匹配当前的分支历史。

设计步骤

1)初始化

基础预测器,初始化为一个大小为2n的两位饱和计数器数组,其中n是索引位数。

标签预测表,多个表,每个表的大小为2m,其中m是索引位数,每个条目包括一个标签和一个计数器。

2)预测过程

索引计算:对于每个标签预测表,使用不同长度的全局历史与分支指令地址组合计算索引。例如,索引计算可以是:index = hash(global_history[:length] + PC),其中 length 是当前表的历史长度,PC 是程序计数器。

标签比较:计算索引后,从每个表中提取条目并比较标签,如果找到匹配的条目,则该表的计数器值用于预测。

选择预测:从匹配的条目中选择使用最长历史匹配的条目进行预测,如果没有匹配的条目,则使用基础预测器的结果。

3)更新过程

更新基础预测器:根据分支实际结果(跳转或不跳转)更新基础预测器的计数器。

更新标签预测表:根据实际结果更新匹配的标签预测表的计数器,如果预测错误,则分配一个新的条目或者替换现有的条目。

4)分配新条目

当预测错误且所有匹配条目的计数器都无法更新时,从不匹配的条目中选择一个条目进行替换,使用全局历史的长度和当前分支地址计算索引和标签,分配新条目。

伪代码示例

class TAGEPredictor:

    def __init__(self, num_tables, history_lengths, table_size):

        self.num_tables = num_tables

        self.history_lengths = history_lengths

        self.table_size = table_size

        self.base_predictor = [0] * table_size

        self.tagged_tables = [self.init_table() for _ in range(num_tables)]

        self.global_history = []

    def init_table(self):

        return [{'tag': None, 'counter': 0} for _ in range(self.table_size)]

    def hash_index(self, pc, history, length):

        return hash((pc, tuple(history[-length:]))) % self.table_size

    def predict(self, pc):

        for i in range(self.num_tables):

            length = self.history_lengths[i]

            index = self.hash_index(pc, self.global_history, length)

            entry = self.tagged_tables[i][index]

            if entry['tag'] == self.get_tag(pc, length):

                return entry['counter'] > 0

        # If no matching entry, use base predictor

        index = hash(pc) % self.table_size

        return self.base_predictor[index] > 0

    def update(self, pc, outcome):

        index = hash(pc) % self.table_size

        self.base_predictor[index] += 1 if outcome else -1

        for i in range(self.num_tables):

            length = self.history_lengths[i]

            index = self.hash_index(pc, self.global_history, length)

            entry = self.tagged_tables[i][index]

            if entry['tag'] == self.get_tag(pc, length):

                entry['counter'] += 1 if outcome else -1

                return

        # If no matching entry to update, allocate new entry

        self.allocate_new_entry(pc, outcome)

    def allocate_new_entry(self, pc, outcome):

        # Simplified allocation logic

        for i in reversed(range(self.num_tables)):

            length = self.history_lengths[i]

            index = self.hash_index(pc, self.global_history, length)

            entry = self.tagged_tables[i][index]

            if entry['tag'] is None:

                entry['tag'] = self.get_tag(pc, length)

                entry['counter'] = 1 if outcome else -1

                return

    def get_tag(self, pc, length):

        return hash((pc, length)) % self.table_size

    def update_global_history(self, outcome):

        self.global_history.append(outcome)

        if len(self.global_history) > max(self.history_lengths):

            self.global_history.pop(0)

# Example usage

num_tables = 4

history_lengths = [4, 8, 16, 32]

table_size = 1024

predictor = TAGEPredictor(num_tables, history_lengths, table_size)

# Simulate some branchesfor pc, actual_outcome in [(0x1234, True), (0x5678, False), (0x9abc, True), (0xdef0, True)]:

    predictor.update_global_history(actual_outcome)

    prediction = predictor.predict(pc)

    print(f"PC: {pc}, Prediction: {prediction}, Actual: {actual_outcome}")

    predictor.update(pc, actual_outcome)

感知机预测器

感知机预测器是一种基于机器学习的分支预测方法,利用感知机来预测分支行为。感知机是一个简单的线性分类器,它可以根据输入的特征(如分支历史)来进行预测。

组成部分

感知机是一种二分类线性分类器,由以下组成部分构成:

权重向量(Weights Vector,w):每个输入特征对应一个权重。

输入向量(Input Vector,x):表示输入的特征,例如分支历史。

偏置(Bias, θ):一个常数,用于调整分类器的决策边界。

预测函数为:y=sign(w*x+θ),其中,w*x 表示权重向量和输入向量的点积。

设计步骤

1)初始化

权重向量初始化,每个分支指令对应一个感知机,其权重向量w初始化为零或小的随机值。

偏置初始化,偏置θ初始化为零或小的随机值。

2)输入向量构建

输入向量x由分支历史组成,通常表示为 +1(跳转)和 -1(不跳转)。例如,假设使用最近的n次分支历史,则输入向量x的长度为n。

3)预测

根据当前的输入向量 x,计算感知机的输出:y=sign(w*x+θ),如果y≥0,预测为跳转;否则预测为不跳转。

4)更新规则

如果预测错误,调整权重向量w和偏置θ:w=w+Δw,θ=θ+Δθ,其中调整量Δw和Δθ根据以下规则确定:

如果实际结果是跳转(+1),则Δw=x,Δθ=1。

如果实际结果是不跳转(-1),则Δw=−x,Δθ=−1。

5)学习率和阈值

在实际实现中,可以引入学习率η来调整权重更新的步长:w=w+ηΔw,θ=θ+ηΔθ,还可以设置一个阈值 T来决定是否进行更新,避免过于频繁的更新导致的不稳定。

伪代码实例

class PerceptronPredictor:

    def __init__(self, history_length):

        self.history_length = history_length

        self.weights = [0] * history_length

        self.bias = 0

        self.history = [0] * history_length

    def predict(self):

        dot_product = sum(w * h for w, h in zip(self.weights, self.history))

        return 1 if dot_product + self.bias >= 0 else -1

    def update(self, actual_outcome):

        prediction = self.predict()

        if prediction != actual_outcome:

            for i in range(self.history_length):

                self.weights[i] += actual_outcome * self.history[i]

            self.bias += actual_outcome

    def update_history(self, outcome):

        self.history.pop(0)

        self.history.append(outcome)

# Example usage

history_length = 16

predictor = PerceptronPredictor(history_length)

# Simulate some branches

for actual_outcome in [1, -1, 1, 1, -1, 1, -1, -1]:

    predictor.update_history(actual_outcome)

    prediction = predictor.predict()

    print(f"Prediction: {prediction}, Actual: {actual_outcome}")

    predictor.update(actual_outcome)

分支目标缓冲器(BTB)

BTB是一种专门用于预测分支目标地址的硬件结构,用于减少分支指令带来的控制相关停顿,提高指令流水线的效率。

主要组件

索引机制:根据程序计数器PC计算BTB的索引,用于快速查找分支目标;常用的方法是对PC取模或使用PC的某些位来直接作为索引。

条目结构:每个BTB条目包含分支指令地址(用于确认该条目是否对应当前的分支指令)、目标地址(预测的分支目标地址)和其他相关信息如分支类型、历史信息。

命中判断:根据PC和存储的地址判断是否命中BTB条目。

更新机制:在分支指令执行后,更新或插入新的BTB条目。

优化策略:使用全关联或组关联缓存来减少冲突,提高命中率;在BTB条目中加入历史信息,以配合其他预测器(如GShare)提高预测准确率,采用返回地址栈RAS用于函数调用和返回的分支预测,维护调用/返回地址堆栈。

伪代码示例

class BTBEntry:

    def __init__(self):

        self.valid = False

        self.branch_pc = None

        self.target_address = None

class BTBPredictor:

    def __init__(self, btb_size):

        self.btb_size = btb_size

        self.btb = [BTBEntry() for _ in range(btb_size)]

    def index(self, pc):

        return pc % self.btb_size

    def predict(self, pc):

        idx = self.index(pc)

        entry = self.btb[idx]

        if entry.valid and entry.branch_pc == pc:

            return entry.target_address

        else:

            return None

    def update(self, pc, target_address):

        idx = self.index(pc)

        entry = self.btb[idx]

        entry.valid = True

        entry.branch_pc = pc

        entry.target_address = target_address

# Example usage

btb_size = 1024

btb_predictor = BTBPredictor(btb_size)

# Simulate some branches

for pc, actual_target in [(0x1234, 0x5678), (0x5678, 0x9abc), (0x9abc, 0xdef0)]:

    predicted_target = btb_predictor.predict(pc)

    print(f"PC: {pc}, Predicted Target: {predicted_target}, Actual Target: {actual_target}")

    btb_predictor.update(pc, actual_target)

返回地址栈(RAS)

RAS是一种专门用于预测函数调用和返回指令目标地址的硬件结构,利用函数调用和返回具有严格配对关系的特点,通过维护一个栈来存储调用返回地址,在函数返回时可以快速预测返回地址。

工作原理

函数调用时:将返回地址(即调用指令的下一条指令的地址)压入栈中。

函数返回时:将栈顶的地址弹出,作为返回地址进行预测。

栈溢出和栈下溢处理:处理RAS满和空的情况,确保预测准确性和系统稳定性。

主要组件

栈结构:用于存储返回地址。

栈指针:指示当前栈顶位置。

压栈操作(Push):在函数调用时执行,将返回地址压入栈中,如果栈未满,增加栈指针;

如果栈已满,处理栈溢出(如丢弃新地址或覆盖旧地址)。

弹栈操作(Pop):在函数返回时执行,从栈顶弹出地址,如果栈不为空,减少栈指针;如果栈为空,处理栈下溢(如返回一个默认地址或错误处理)。

循环栈代码示例

(将栈设计为循环结构,当栈满时从头开始覆盖旧地址)

class CircularReturnAddressStack:

    def __init__(self, size):

        self.size = size

        self.stack = [None] * size

        self.pointer = 0

        self.count = 0

    def push(self, address):

        self.stack[self.pointer] = address

        self.pointer = (self.pointer + 1) % self.size

        if self.count < self.size:

            self.count += 1

    def pop(self):

        if self.count > 0:

            self.pointer = (self.pointer - 1 + self.size) % self.size

            address = self.stack[self.pointer]

            self.stack[self.pointer] = None

            self.count -= 1

            return address

        else:

            print("RAS Underflow")

            return None

    def predict(self):

        if self.count > 0:

            return self.stack[(self.pointer - 1 + self.size) % self.size]

        else:

            return None

# Example usage

circular_ras = CircularReturnAddressStack(ras_size)

# Simulate function calls and returns

for addr in call_addresses:

    circular_ras.push(addr)

for _ in range(len(call_addresses)):

    predicted_address = circular_ras.predict()

    actual_address = circular_ras.pop()

    print(f"Predicted Return Address: {predicted_address}, Actual Return Address: {actual_address}")

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值