[处理器芯片]-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}")

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/16256.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mysql-mysqldump逻辑备份

mysqldump逻辑备份---- 推荐优先使用(重点) mysqldump 是 MySQL 自带的逻辑备份工具。可以保证数据的一致性和服务的可用性。 逻辑备份不占空间 比较快捷 不需要停服务 可以正常导入导出 cd /usr/local/mysql/bin 可以查看 mysqlpump 产生测试库与表 测试表:company.emplo…

详细分析Element Plus中的ElMessageBox弹窗用法(附Demo及模版)

目录 前言1. 基本知识2. Demo3. 实战4. 模版 前言 由于需要在登录时&#xff0c;附上一些用户说明书的弹窗 对于ElMessageBox的基本知识详细了解 可通过官网了解基本的语法知识ElMessageBox官网基本知识 1. 基本知识 Element Plus 是一个基于 Vue 3 的组件库&#xff0c;其中…

20240523每日运维--------聊聊docker简介(一)

dotCloud 说Docker&#xff0c;必不可免不得不说dotCloud&#xff0c;Docker本来只是dotCloud公司的内部项目&#xff0c;其公司创始人 Solomon Hykes 发了一个内部项目&#xff0c;而这个项目就是Docker&#xff0c;自从2013年docker开源以后&#xff0c;在世界范围引起相当轰…

对于高速信号完整性,一块聊聊啊(12)

常见的无源电子器件 电子系统中的无源器件可以按照所担当的电路功能分为电路类器件、连接类器件。 A、电路类器件&#xff1a; &#xff08;1&#xff09;二极管&#xff08;diode&#xff09; &#xff08;2&#xff09;电阻器&#xff08;resistor&#xff09; &#xf…

浅谈对称加密非对称加密

对称加密&#xff1a;加密和解密使用的密钥是同一个 常见算法&#xff1a;DES、3DES、Blowfish、IDEA、RC4、RC5、RC6 和 AES非对称加密&#xff1a;需要两个密钥&#xff0c;一个公开密钥、一个私有密钥 常见算法&#xff1a;RSA、ECC&#xff08;移动设备用&#xff09;、Dif…

vue3 ant ts 集成wavesurfer

实现功能有&#xff1a;音词同步&#xff0c;倍速播放&#xff0c;拖拽播放&#xff0c;快进\退 &#xff0c;重播&#xff0c;显示总 时长&#xff0c;关闭页面时关闭声音等功能 package.json 引入 "wavesurfer.js": "^7.7.14", 父页面引入自己封的 MyWa…

将推荐算法应用到llm上之comi_rec_sa

参考地址 https://aistudio.baidu.com/projectdetail/7943516 数据生成 import pandas as pd import numpy as npimport json import numpy as np with open("唐诗.json","r",encoding="utf-8") as f:data

深度讲解Spring Bean扫描类:源码深度剖析与实战策略

1. 引言 在Spring框架中&#xff0c;Bean的扫描是一个至关重要的过程&#xff0c;它决定了哪些类会被Spring容器管理并作为Bean实例化。对于高级Java工程师而言&#xff0c;深入理解这一过程不仅有助于提升对Spring框架的掌握程度&#xff0c;还能在实际开发中更加灵活地运用S…

归并排序算法(经典、常见)

今天我们不刷力扣了&#xff0c;我们来复习&#xff08;手撕&#xff09;一下数据结构中的八大排序算法之一&#xff0c;归并排序 基本概念&#xff1a; 归并排序&#xff08;Merge sort&#xff09;是建立在归并操作上的一种有效的排序算法&#xff0c;该算法是采用分治法&am…

SpringBoot中如何在服务器进行校验?

数据校验就是数据的合法性检查&#xff0c;在服务器端也可以对数据进行校验&#xff0c;一般使用JSR303 校验 JSR303是Java为Bean数据合法性校验提供的标准框架&#xff0c;是一种声明式校验 JSR303通过在Bean属性上标注类似于NotNull、Max等注解来指定校验规则&#xff0c;并…

小苯的排列构造(最大公约数,构造,数学推导)

文章目录 题目描述输入格式输出格式样例输入1样例输出1提交链接提示 解析参考代码 题目描述 格格有一个长度为 n n n 的排列 p p p&#xff0c;但她不记得 p p p 具体的样子&#xff0c;她只记得数组 a a a。 其中&#xff1a; a i g c d ( p 1 , p 2 , . . . , p i ) a…

【网络技术】【Kali Linux】Wireshark嗅探(十五)SSDP(简单服务发现协议)报文捕获及分析

往期 Kali Linux 上的 Wireshark 嗅探实验见博客&#xff1a; 【网络技术】【Kali Linux】Wireshark嗅探&#xff08;一&#xff09;ping 和 ICMP 【网络技术】【Kali Linux】Wireshark嗅探&#xff08;二&#xff09;TCP 协议 【网络技术】【Kali Linux】Wireshark嗅探&…

与MySQL DDL 对比分析OceanBase DDL的实现

本文将简要介绍OceanBase的DDL实现方式&#xff0c;并通过与MySQL DDL实现的对比&#xff0c;帮助大家更加容易理解。 MySQL DDL 的算法 MySQL 的DDL实现算法主要有 copy、inplace和instant。 copy copy算法的实现相对简单&#xff0c;MySQL首先会创建一个临时表&#xff0…

C++:STL

STL 文章目录 STLSTL 绪论迭代器&#xff08;iterators&#xff09;容器&#xff08;Containers&#xff09;vectorset,multisetmap,multimapstackqueuedequepriority_queuebitset 算法&#xff08;Algorithms&#xff09;sort,count,find,lower_bound,upper_bound,binary_sear…

(2024,attention,可并行计算的 RNN,并行前缀扫描)将注意力当作 RNN

Attention as an RNN 公众号&#xff1a;EDPJ&#xff08;进 Q 交流群&#xff1a;922230617 或加 VX&#xff1a;CV_EDPJ 进 V 交流群&#xff09; 目录 0. 摘要 3. 方法 3.1 注意力作为一种&#xff08;多对一的&#xff09;RNN 3.2 注意力作为&#xff08;多对多&…

多语言印度红绿灯系统源码带三级分销代理功能

前端为2套UI&#xff0c;一套是html写的&#xff0c;一套是编译后的前端 后台功能很完善&#xff0c;带预设、首充返佣、三级分销机制、代理功能。 东西很简单&#xff0c;首页就是红绿灯的下注页面&#xff0c;玩法虽然单一&#xff0c;好在不残缺可以正常跑。

Putty: 随心御剑——远程启动服务工具plink

一、引言:如何远程控制 也许你会有这样的场景,交互程序(以下简称UI程序)跑在windows端,而控制程序跑在Linux上。我们想要通过windows端 UI程序来启动Linux下面的服务,来一场酣畅淋漓的御剑飞行咋办,难道要自己十年磨一剑,在Linux下编写一个受控服务程序么.计算机科技发…

【MATLAB】信号的熵

近似熵、样本熵、模糊熵、排列熵|、功率谱熵、奇异谱熵、能量熵、包络熵 代码内容&#xff1a; 获取代码请关注MATLAB科研小白的个人公众号&#xff08;即文章下方二维码&#xff09;&#xff0c;并回复信号的熵本公众号致力于解决找代码难&#xff0c;写代码怵。各位有什么急需…

FreeRTOS中断中释放信号量

串口接收&#xff1a;中断程序中逆序打印字符串 串口接收&#xff1a;逆序回环实验思路 注&#xff1a;任务优先级较高会自动的切换上下文进行运行 FreeRTOS中的顶半操作和底半操作 顶半操作和底半操作“这种叫法源自与Linux”在嵌入式开发中&#xff0c;为了和Linux操作系统做…

kafka 案例

kafka 案例 目录概述需求&#xff1a; 设计思路实现思路分析1.kafka案例_API 带回调函数的生产者2.kafka案例_API生产者分区策略测试3.kafka案例_自定义分区的生产者4.kafka案例_API同步发送生产者5.kafka案例_API简单消费者5.kafka案例_API消费者重置offset 参考资料和推荐阅读…