CPU流水线与分支预测

1.CPU指令流水线

CPU在执行指令的时候,一条指令并不是一下就完成的,会有生命周期,例如很经典的有MIPS五级流水线,一条指令执行完毕需要五步

  1. 取指(instruction fetch):将指令地址从程序计数器(ProgramCounter)取出来,送上地址总线,再有地址总线找到对应的主存单元,取出指令码,再通过数据总线传给指令寄存器(IR)。
  2. 译码(instruction decode):把指令寄存器中的指令交给译码器进行翻译,如果是数值操作的话,会读出待操作的数值,例如a+b,会把a和b的值都通过数据总线发给暂存寄存器(TR)中。
  3. 执行(instruction execute):指令译码之后所需要进行的计算类型都已得知,并且已经从通用寄存器组中读取了所需的操作数,下面就是指令执行。执行就是真正计算的过程,例如如果指令是一条加法运算,则对操作数进行加法操作。
    “执行”阶段,最常用的部件就是算数逻辑部件运算器(Arithmetlc Logical Unit, ALU),作为实施具体运算的硬件功能单元。
  4. 访存(memmory access):存储器访问指令往往是指令集中最重要的指令类型之一,访存是指存储器访问指令将数据从存储器中读取者写入存储器的过程
  5. 写回(write-back):将指令执行的结果写回通用寄存器组的过程。如果是普通运算指令,该结果值来自“执行”阶段计算的结果;如果是存储器读指令,该结果来自于“访存”阶段从存储器中读取出来的数据。

这五步中的前三步,在这个网页中有更好的说明,还有图。计算机指令在CPU中的执行过程(图文版)

如果是普通的逻辑,就是当一条指令执行完“写回”之后再进行下一条指令的“取指”,但是这样会有很大的浪费,因为第一条指令在执行译码的时候,取指、执行、访存和写回都是空着的。下图就是一条指令执行过程中的硬件电路的原理图。

pic1-1

理想情况下,最好的办法就是,第一条指令只要开始执行译码,第二条指令就进来,第一条指令进入执行的时候,第二条指令进入译码,第三条指令进入取指。一个五级流水线结构在七个机器周期内有三个指令被全部执行完毕。如下图所示的效果

pic1-2

2. 分支预测的方法

2.1 分支预测的作用

正如上面所说,CPU流水线的存在,第一条指令只要过了取指,第二条指令就进入流水线了,但是第一条指令还没执行完呢,第二条指令就进来,进啥呢?我们都不知道,CPU肯定更不知道了,就只能猜,这个猜就是分支预测。如果猜对了,那很好,执行效率会很高,如果猜错了,就要把已经装填好的指令全部退掉,重新装填,成本也是很高的。不管怎么做肯定都是猜,所以最好的办法肯定是尽量避免分支预测

2.2 分支预测的方法
2.2.1 静态预测

不同的处理器会有不同的处理方式,SPARC和MIIPS最要用的方法是:预测条件跳转不会发生,因此总是顺序取下一条指令推测执行,仅当条件跳转指令被求值确实发生了跳转,才会把非顺序的指令插入。分支预测的过程发生在执行阶段之前,在其中插入一个分支延迟间隙,也就是发现错误退出的时候,这个指令也已经执行过两次了。

这个方法看起来就比较笨。

2.2.2 动态预测

利用分支指令发生转移的历史进行预测,并根据实际执行情况,进行动态调整,目前所有的处理器都用动态预测,这个的准确率能到90%。

2.2.3 其它预测

剩下的还有饱和计数、两级自适应预测、局部预测、全局预测很多很多,这都是根据硬件决定的。

3. 分支预测的实例

按照前面介绍的分支预测方法,大部分都是根据之前的行为来预测后续的行为,也就是说当输入的数据是大量且无规律的,同时有参数判断过程的存在,下一次执行的指令就不确定了,就可以认为有一大部分的数据都会分支预测错误。因此解决问题的方法就有两个:

  1. 让数据变得有序:这个有序并不一定指的就是sort,而是数组前半段都是满足判断条件的,这样分支预测就只会发生一次,几乎没影响,不过这种“有序”的生成本身就是有很大代价的,如果要用sort的话,复杂度变成 n l o g 2 N nlog_2^N nlog2N了,耗时会明显升高,最好从数据来源上就保证有序;
  2. 取消判断:如果能在逻辑上取消肯定是最好的,但是如果不行,就可以用其它方式取消,例如位运算。下面的例子就是这样的一种,例如需要统计其中小于0的全部数据之和,每次待判断的数据记为val,若val是int类型的,当将val右移31位,若其为负数将为0xffff,若其为非负数将为0x00,而0x00按位与val将为0,0xffff按位与val还是原来的数,就能用这个移位操作来当作int符号的判断操作。
#include<chrono>
#include<iostream>
#include<algorithm>
#include<vector>
#include<cstdlib>
#include<time.h>

#define NUM 1000000
#define VEC_CONTENT  1000

void randomCase(const std::vector<int>& randomVec)
{
	auto start = std::chrono::system_clock::now();
	long long  minusSum = 0;
	for(auto valVec : randomVec)
		if(valVec<0)
			minusSum += valVec;

	auto end = std::chrono::system_clock::now();

	std::cout<<"randomCase's result is "<<minusSum<<", and time cost is ";
	std::cout<<std::chrono::duration_cast<std::chrono::milliseconds>(end-start).count();
	std::cout<<"ms"<<std::endl;
	return ;
}

void sortedCase(const std::vector<int>& randomVec)
{
	std::vector<int>sortedVec = randomVec;
	std::sort(sortedVec.begin(), sortedVec.end());
	
	auto start = std::chrono::system_clock::now();
	long long minusSum = 0;

	for(auto valVec : sortedVec)
		if(valVec<0)
			minusSum += valVec;

	auto end = std::chrono::system_clock::now();

	std::cout<<"sortedCase's result is "<<minusSum<<", and time cost is ";
	std::cout<<std::chrono::duration_cast<std::chrono::milliseconds>(end-start).count();
	std::cout<<"ms"<<std::endl;
	return ;
}


void bitCase(const std::vector<int>& randomVec)
{
	std::vector<int>sortedVec = randomVec;
	auto start = std::chrono::system_clock::now();
	long long minusSum = 0;

	for(auto valVec : sortedVec)
		minusSum += (valVec>>31) & valVec;

	auto end = std::chrono::system_clock::now();

	std::cout<<"bitCase's result is "<<minusSum<<", and time cost is ";
	std::cout<<std::chrono::duration_cast<std::chrono::milliseconds>(end-start).count();
	std::cout<<"ms"<<std::endl;
	return ;
}

void vecCreate(std::vector<int>& randomVec)
{
	size_t n = randomVec.size();
	for(size_t i=0; i<n; ++i)
		randomVec[i] = std::rand()%((VEC_CONTENT<<1) + 1) - VEC_CONTENT;
}

int main(void)
{
	std::srand((unsigned)time(nullptr));
	std::vector<int>randomVec(NUM);
	vecCreate(randomVec);

	randomCase(randomVec);
	sortedCase(randomVec);
	bitCase(randomVec);
	return 0;
}

没用O2的时候输出结果如下

randomCase's result is -249441649, and time cost is 17ms
sortedCase's result is -249441649, and time cost is 8ms
bitCase's result is -249441649, and time cost is 9ms

可见排好序之后速度会快很多,和bitcase的效果差不多。不过如果把排序时间算上就比random慢了,但是我看到说有的时候把排序时间算上还比randomcase快的,但不知道是不是和java有关系。

这个分支预测的问题,一般只会发生在大量无序数据判断的时候。

那既然if可以用位预算提速,是不是所有的if都要换呢?我觉得这样并不好,因为如果数据量不大,对效率的提升微乎其微,而且最大的问题是可读性会变得很差

部分资料和图来自下面的网站

https://zhuanlan.zhihu.com/p/109574885

https://zh.wikipedia.org/wiki/%E5%88%86%E6%94%AF%E9%A0%90%E6%B8%AC%E5%99%A8

https://blog.csdn.net/hanzefeng/article/details/82893317

  • 5
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 流水线分支预测是一种在计算机体系结构中常用的技术,用于优化指令执行的效率。在使用Vivado进行流水线分支预测时,我们可以通过以下几个步骤来实现。 首先,需要明确流水线分支预测的目标是为了减少由于条件分支导致的指令流水线的停顿,从而提高程序的执行效率。在采用了流水线分支预测技术后,我们可以在分支指令的执行过程中预测分支的目标地址,并提前取出并执行该目标地址处的指令,从而减少流水线的停顿。 其次,Vivado提供了丰富的资源和工具来帮助我们实现流水线分支预测。其中,重要的组件之一是分支目标缓冲器(Branch Target Buffer,BTB),它可以存储分支指令的目标地址,并根据历史分支的结果来预测分支的目标地址。此外,Vivado还提供了分支预测器,它可以根据分支指令的特征来确定是否进行分支预测,并选择适当的预测策略。 在进行流水线分支预测时,我们还需要考虑分支指令静态特征和动态特征。静态特征包括分支指令的类型和位置,动态特征则包括分支历史信息,如分支的执行结果。通过综合考虑静态特征和动态特征,我们可以选择合适的流水线分支预测策略,提高分支预测的准确性。 最后,我们需要进行仿真和验证,以确保流水线分支预测的正确性和有效性。通过运行实际程序和对比预测结果和实际结果,我们可以评估分支预测的准确性,并对优化策略进行调整。 总而言之,流水线分支预测vivado是一种有效的优化技术,可以提高分支指令的执行效率。通过合理选择分支预测策略,使用Vivado提供的工具和资源,我们可以实现准确、高效的分支预测。 ### 回答2: 流水线分支预测是指在计算机流水线操作中,为了解决分支指令带来的流水线停顿,采用“猜测”的方式来提前执行分支指令。Vivado是一种适用于FPGA设计的开发软件,通过Vivado可以对FPGA的设计进行综合、实现和生成比特流文件。 在流水线中,分支指令可能会导致流水线中断,因为在执行分支指令之前,无法确定分支的目标地址。为了充分利用流水线的并行性,可以采用分支预测的方法来提前执行分支指令分支预测的基本原理是根据历史分支的结果,采用一定的算法对下一次分支的目标进行预测。如果预测正确,则可以提前执行分支指令;如果预测错误,则需要进行流水线中断和清空部分流水线指令。 Vivado是一种先进的FPGA设计开发软件,它提供了丰富的功能和工具,可以实现高效的FPGA设计。在Vivado中,流水线分支预测可以通过在设计中增加预测器来实现。预测器可以根据历史分支的结果进行训练,然后根据训练结果对下一次分支进行预测。通过使用Vivado中的分支预测功能,可以使得FPGA的设计在执行分支指令时可以更好地利用流水线的并行性,提高整体的性能。 总之,流水线分支预测是为了解决分支指令带来的流水线停顿问题而采用的一种技术。在Vivado中,可以通过增加预测器来实现流水线分支预测,从而提高FPGA设计的性能。 ### 回答3: Vivado 是赛灵思(Xilinx)公司推出的一款用于FPGA(现场可编程门阵列)开发的综合工具。流水线分支预测是一种用于提高CPU性能的技术,在Vivado 中也得到了支持。 流水线分支预测是指在程序执行过程中,当遇到条件分支指令(如if语句或循环)时,预测分支的方向,从而在程序继续执行之前预测下一条指令的位置。这种预测机制可以减少分支带来的流水线停顿,提高CPU指令执行速度。 在Vivado 中,流水线分支预测的实现是通过逻辑电路和控制器相结合来完成的。Vivado 的软件工具提供了一些优化技术,如自动路由和时序优化,以便实现高效的流水线分支预测。这些技术可以帮助开发人员在FPGA中实现更好的预测性能,并最大程度地减少流水线停顿。 通过在Vivado 中配置适当的参数和设置,开发人员可以根据具体的应用需求来调整流水线分支预测的性能。Vivado 还提供了大量的文档和教程,以帮助开发人员理解和使用流水线分支预测功能。 总之,Vivado 是一款强大的FPGA开发工具,提供了流水线分支预测技术的支持。通过使用Vivado,开发人员可以有效地实现流水线分支预测,提高CPU性能和指令执行速度。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tux~

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值