CPU的分支预测

前言

最近在进行性能调优的时候, 碰到了这样的一段代码(为了展示问题而简化的代码):

<?php
// 第一次运行
$start = microtime(true);
for ($i = 0; $i < 100; $i++) {for ($j = 0; $j <1000; $j++) {for ($k = 0;$k < 10000; $k++) {}}
}
$end = microtime(true);
echo 'first time is ', PHP_EOL;
echo $end - $start, PHP_EOL;
// 第二次运行
$start = microtime(true);
for ($i = 0; $i < 10000; $i++) {for ($j = 0; $j <1000; $j++) {for ($k = 0;$k < 100; $k++) {}}
}
$end = microtime(true);
echo 'second time is ', PHP_EOL;
echo $end - $start, PHP_EOL;

其中的两次运行操作基本一致, 均完成了十亿次循环. 按理说他们的运行时间应该是一样的呀. 但是, 浅看结果:

image-20220609215241430

虽然每次运行的时间都不一样, 但是, 第二种方式每次都要比第一种多出来2-3秒.

what? why? 于是我想, 有没有可能是运行顺序的原因. 因此我将第二种放到前面运行, 结果也相应的颠倒了.

因此, 得出结论. 多层遍历时, 相比于由大到小(外圈大, 内圈小), 由小到大要运行的快一些. 上一边子去, 这算哪门子结论. 作为一个文化人, 自然要知道为什么会出现这种现象.

解释

为了进行揭秘, 我将代码改造了一下, 通过C来查看, 毕竟C程序可以直观的看到汇编结果. 测试程序如下:

#include "stdio.h"
#include "sys/time.h"int main(){struct timeval t;int n;long startTime, endTime;// 第一次运行gettimeofday(&t,NULL);startTime = t.tv_sec * 1000 + t.tv_usec / 1000;n =  0;for (int i=0; i < 100; i++) {for(int j = 0; j < 1000; j++){for (int k = 0; k < 10000; k++){n++; // 为了防止 CPU 优化, 在循环中做了自增的操作}}}gettimeofday(&t,NULL);endTime = t.tv_sec * 1000 + t.tv_usec / 1000;printf("first use time: %ld\n", endTime - startTime);// 第二次运行gettimeofday(&t,NULL);startTime = t.tv_sec * 1000 + t.tv_usec / 1000;n=0;for (int i=0; i < 10000; i++) {for(int j = 0; j < 1000; j++){for (int k = 0; k < 100; k++){n++;}}}gettimeofday(&t,NULL);endTime = t.tv_sec * 1000 + t.tv_usec / 1000;printf("second use time: %ld\n", endTime - startTime);
}

image-20220609220106269

结果确实是第二种方式要慢上一些. 这里顺带提一句, 相同的操作, PHP程序跑了20多秒, C程序只用了不到2秒, 而且还进行了变量自增的操作. 果然, 脚本语言的性能还是差点意思啊.

我使用C编写程序, 另一个原因是为了验证这不是PHP语言特有的问题, 以进一步确认查找问题的方向.

好, 废话不多说, 经过多方查找, 最终晓得了为什么. 要解释这个现象, 还要从CPU的运行说起.

CPU 流水线

对于CPU的流水线不做深入解释, 简单过一下了.

CPU在执行指令的时候, 要经过多个步骤, 下面是一个简化的步骤:

  1. 取指令
  2. 指令译码
  3. 执行指令
  4. 输出结果

如果说, CPU为了充分利用时钟周期, 不让其空闲, 将流程改造成了一条流水线, 既取指操作将结果交给译码器之后, 不会等待指令执行结束, 而是立即读取下一条指令, 以提高执行效率. 跑起来大概就是这样:

image-20220609222040282

在同一时间, 多个流水线部件同时工作.

对于CPU的工作流水线, 还有一些问题, 比如指令的依赖, 下一条指令的输入是上一条指令的输出 等等问题, 在这篇文章中按下不表.

指令预测

好, 现在知道了CPU是通过流水线执行的. 但这个流水线碰到比较的跳转指令就不好使了.

比如有一个条件判断指令, 若为真则跳转到 B 执行, 否则继续执行. 此时, CPU这条流水线如何处理呢? 碰到这种情况不进行处理, CPU空转等待下一条指令也是可以的. 但是还是有些浪费CPU性能.

假设条件判断的两种情况概率各50%, CPU也可以假设条件判断后继续执行, 若预测正确, 则皆大欢喜. 若预测错误, 将预先执行的操作销毁后重新执行即可. 这样, 只要销毁的操作开销不大, 就可以提高整体性能.

image-20220609224942574

但是, 前辈们对于50%的预测成功概率是不能满足的. 因此又提出了动态预测, 也就是说依据历史来进行预测, 若上一次为真, 则本次为真, 若上次为假, 则本次为假. 如此可将预测成功率提高至90%. (现代CPU 的预测实现会更复杂一些)

对于指令的预测详情可查看维基百科的解释: 分支預測器

for 指令实现

在有了CPU指令预测概念后, 我们通过一段小小的C程序, 来看一下for循环是如何实现的:

int main(){int n = 0;for (int i=0; i < 100; i++) {n++;}
}

通过命令查看其汇编结果

gcc -g for-simple.c && objdump -dSl ./a.out

image-20220609230925482

这就是用来实现for循环遍历的逻辑了. 113d步将i变量加一, 然后红框部分判断i<100, 若成立则跳转到1139继续执行.

也就是说, 在1145这个地方, 发生了我们上面所述分支预测的情况. 既在for循环结束时, 判断是回到开始位置继续遍历, 还是继续执行, 此时需要进行预测. 按照我们前面的推论, CPU按照动态预测, 根据历史进行预测. 则前99次结果均相同, 继续循环, 最后一次退出循环时预测失败. 对于一次普通的for循环, 预测失败仅1次. 成功率还是蛮高的哈.

解惑

好, 现在已经准备充分, 可以回过头来重新看看我们前面的例子, 对于这样的一个循环(动态预测方式):

int main(){for (int i=0; i < 100; i++) {for(int j = 0; j < 1000; j++){for (int k = 0; k < 10000; k++){// CPU 在这里进行分支预测, 仅最后一次预测失败. 共失败1次}// CPU 在这里进行分支预测, 共失败999次, 最后一次成功// 因为上一次第三层循环退出, 因此本次预测退出循环, 但结果继续第二层循环}// CPU 在这里分支预测, 共失败99次}
}

(这里为了方便计算, 上面的99都近似为100进行计算了, 否则数字根本没法看)

则, 共预测失败100*1000*1=10^5次.

再计算一下, 若最外层循环10000次呢? 则会预测失败: 10000*1000*1=10^7次.

前面也提到了, 当预测失败时, 需要将预测的已执行内容销毁, 占用些许的CPU时钟周期. 同时被销毁的已执行内容也已经消耗了时钟周期. 因此, 相同的逻辑, 预测失败次数多的, 相应其耗时就会增加.


简单了解一下, 果然, 任何一个不平常的现象后面, 都有其出现的原因.

原文地址: https://hujingnb.com/archives/819

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

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

相关文章

PHP获取Opcode及C源码

是什么 在开始之前, 必须要先介绍一下Opcode是什么. 众所周知, Java在执行的时候, 会将.java后缀的文件预先编译为.class字节码文件, JVM加载字节码文件进行解释执行. 而字节码文件存在的意义, 就是为了加速执行. 那么PHP的Opcode与之类似, 也是从.php文件到执行的过程中, 所…

PHP require/include 区别

前言 在PHP中, 载入文件可以选择使用require, 也可以使用include, 那么那他们有什么区别呢? 看了网上的一些文章, 说他们使用场景不同, require一般在文件开头引入文件, include一般在函数中动态引入文件. 但是我觉得并不是这么简单, require是作为语言结构(关键字)出现的, …

Golang 接口原理

问题 小提示, 若想直接查看原理, 可从接口原理开始查看. 有这样一段GO代码: func main() {var obj interface{}fmt.Printf("obj nil. %b\n", obj nil)type st struct{}var s *stobj sfmt.Printf("s nil. %b\n", s nil)fmt.Printf("obj nil. …

三星识别文字_比亚迪电子助力三星Galaxy Note 10系列霸气首发!

三星有子初长成气宇轩昂 秀美俊逸减之一分则嫌柔增之一分则嫌赘2019年8月7日于纽约巴克莱发布Galaxy Note 10系列用简约 重构美三星Galaxy Note 10与Galaxy Note 10分别搭载了6.3英寸和6.8英寸的超感官全视曲面屏&#xff0c;均采用单摄挖孔屏&#xff0c;开孔位于屏幕正上方。…

lisp 设计盘形齿轮铣刀_机械设计基础——周转轮系传动比的计算

点击上方蓝色字体&#xff0c;关注我们15(视频来源于网络&#xff0c;仅供学习交流&#xff0c;侵权请联系删除)机械计重点学习指导机械原理全书重点提要轴的结构改错机械设计作业集01机械设计作业集02机械设计作业集答案机械原理作业集机械原理作业集答案轴的强度计算院校推荐…

b+树阶怎么确定_B站公布年度弹幕,这个排名我不太服气

也忘记了是从什么时候开始&#xff0c;B站开始公布自己的年度弹幕了&#xff0c;今年的年度弹幕排名前五的分别是&#xff1a;爷青回、武汉加油、有内味了、双厨狂喜、禁止套娃。话说今年真的是不容易啊&#xff0c;过年那段时间以及上半年不会忘记那一幕幕感人深邃的瞬间&…

css打印适应纸张_从生态平衡到打印机故障分析

生态平衡(ecological equilibrium)是指在一定时间内生态系统中的生物和环境之间、生物各个种群之间&#xff0c;通过能量流动、物质循环和信息传递&#xff0c;使它们相互之间达到高度适应、协调和统一的状态。也就是说当生态系统处于平衡状态时&#xff0c;系统内各组成成分之…

html5调用系统声音1s响一次_20款奔驰GLC260提车改柏林之声音响,音乐诉请,为爱发声!...

奔驰GLC车型在2020上半年可谓是风生水起&#xff0c;尤其是2020年1-5月份的豪华品牌SUV排名中&#xff0c;奔驰GLC车型以58982的销售量遥遥领先&#xff0c;同比增长了2%&#xff0c;奔驰GLC5月销量高达15275辆&#xff0c;再次打败老对手奥迪Q5L&#xff0c;夺得豪华SUV销量冠…

kotlin将对象转换为map_将网站转换为Photoshop文档

WebToLayers是一款能够帮助大家将网页转换成图像格式的软件&#xff0c;能够Web页面转换成PNG&#xff0c;JPG以及PSD格式的图片。当网页转换为PSD的时候&#xff0c;网页的各个要素都会自动转换为相应的图层&#xff0c;使得大家能够对PSD格式的网页进行设计与管理。WebToLaye…

centos更换网卡后怎么更新配置_CentOS安装

服务器使用的Linux操作系统都使用了CentOS来进行安装&#xff0c;CentOS是一个开源的Linux发行版&#xff0c;具有很好的稳定性和更多的可扩展行。为了能够正常使用Docker&#xff0c;我们将使用CentOS7及以上版本。​下载地址&#xff1a;https://www.centos.org/download/ ​…

centos普通用户修改文件权限_Linux实战014:Centos创建用户并添加root授权

刚收到在腾讯云申请的云服务器8台&#xff0c;现在准备分配给不同项目组来使用。为了确保系统及账号的安全&#xff0c;root账号不能直接给到他们。因为root的权限太大&#xff0c;任何的误操作就可能导致系统异常或者数据丢失找不回来。而且我们这是生产环境&#xff0c;账号会…

mongodb 导出txt_(干货)前端实现导出excel的功能

前言 导出功能其实在开发过程中是很常见的,平时我们做导出功能的时候基本都是后台生成&#xff0c;我们直接只需要调一支接口后台把生成的文件放到服务器或者数据库mongodb中,如果是放到mongodb中的话,我们需要从mongodb中通过唯一生成的id去拿到文件,最后window.location.href…

1971旗舰cpu intel_CPU的历史

很多人都对电脑硬件有一点的了解&#xff0c;本人也算略懂一二&#xff0c;所以今天来为大家说说电脑的主要硬件之一––CPU(中央处理器)。那么我们知道世界上造CPU的公司主要就是Intel和AMD。其实仔细想想&#xff0c;CPU的主要成分是什么?是硅(Si)&#xff0c;硅从那里来&am…

文本显示变量_【RPA课堂】UiPath中的变量、数据类型和组件

自动化出现的那一天起&#xff0c;就有了各种各样的工具来满足自动化的需要。无论是用于windows桌面自动化的简单工具&#xff0c;还是用于企业自动化大量任务的工具&#xff0c;它们都有自己的功能。UiPath就是这样的工具&#xff0c;在本文中&#xff0c;我们介绍一些非常基本…

bootstrap上传图片可实现查看上一张图片和下一张图片_如何实现像人民日报微信推文一样的的点亮效果?...

如何实现向人民日报微信推文一样的的点亮效果&#xff1f;有两种方法&#xff1a;方法一&#xff1a;就是使用代码在编辑器进行编辑emmmmmm这个方法贼麻烦&#xff0c;需要调至HTML模式……方法二&#xff1a;在现有编辑器模板下利用SVG动画进行编辑&#xff0c;因为点亮效果本…

设置log缓存_node多级缓存之redis缓存

在node项目开发过程中&#xff0c;缓存常常被用来解决高性能、高并发等问题。在我们的实际项目中&#xff0c;运用缓存的思路是内存缓存-->接口-->文件缓存。前面的总结中已经详细的说明了怎么实现和封装内存缓存和文件缓存。虽然二级缓存已经基本能够满足现在的所有场景…

c++实现决策树分类汽车评估数据集_R有监督机器学习-分类方法

当我们说机器学习的的时候&#xff0c;我们在说什么&#xff1f;来源于mlr3包的作者&#xff1a;https://mlr3book.mlr-org.com/basics.html上图解释了完整的机器学习流程&#xff0c;包括构建任务、准备训练数据集及测试数据集、选择学习方法&#xff08;leaner&#xff09;、…

lingo编程的主要方法_java并发编程 --并发问题的根源及主要解决方法

并发问题的根源在哪首先&#xff0c;我们要知道并发要解决的是什么问题&#xff1f;并发要解决的是单进程情况下硬件资源无法充分利用的问题。而造成这一问题的主要原因是CPU-内存-磁盘三者之间速度差异实在太大。如果将CPU的速度比作火箭的速度&#xff0c;那么内存的速度就像…

Mysql中Drop删除用户的名字_mysql5.5 使用drop删除用户

在说这个问题之前我们先讨论下关于在mysql中删除用户的方法和问题&#xff1a;其实在以前我删除mysql中的账号的时候用delete&#xff0c;一直没注意其实用这个命令删除账号会有一个问题就是使用delete删除账号后&#xff0c;只会清除user表的&#xff0c;在其它表中的信息还是…

docker建多个mysql_《容器化系列二》利用Docker容器化技术安装多个mysql

前提说明安装的Linux系统版本为Centos7.x一、安装docker并测试1、安装yum相关工具包///安装yum相关工具包yum install -y yum-utils device-mapper-persistent-data lvm2//发些报错&#xff0c;关闭刚刚睡眠中的进程kill -9 13312//再次执行yum install -y yum-utils device-ma…