深入计算机系统看性能优化

一.引言

“性能优化”,从计算机诞生之初就一直伴随着计算机技术的发展,直到现在。将来也必定不会消失。这是因为每个人都会追求性价比,花最少的钱,办最多的事。生活中也一样,就比如说泡茶,但凡有点常识的人都不会先洗茶杯,再去烧水,而是先去烧水,在等水开的过程中,去做洗茶杯等工作。这也是一种优化。

二.访问寄存器代替内存引用

我们先看一个例子:

有这么两个程序:它们的目的就是将数组x中的数,按照下标累加到数组y中,最后在把数组y中的数据累加到一个数dest里面。为了验证效果,我们将这个过程重复10000遍。

Prog 1 Prog2

这两个程序的区别就在Prog2中红框里面的内容。那么哪个程序运行的更快呢?

话不多说,我们看实际的结果:

这里为了说明效果,我们编译的时候,并没有采用优化(编译优化,确实可以提高程序运行的效率,但是过高的编译优化等级会有一定的副作用,另外编译器优化也具有一定的局限性,高效的代码仍然应该是我们追求的目标)。可以看到,Prog2要明显比Prog1快。

要想理解上面的例子,我们必须先介绍一下寄存器和汇编代码的相关知识:

1.寄存器

CPU内部用来存放数据的一些小型存储区域, 注意寄存器是在CPU内部,受限于CPU的物理尺寸,寄存器数量不会太多。我们只需要记住两点:

1) 寄存器和CPU的L1 cache相比,速度虽然还在一个数量级,但是L1 cache的访问速度还是要慢几倍。具体的数据见下文表2

2) CPU只能从寄存器直接取数据或者指令,如果取不到,获取的顺序是L1->L2->L3->主存->磁盘。

从下文表2中可以看出,如果cpu的cache访问miss了,性能损失还是很大的。如果内存里面再miss了,那对性能来说不亚于一场灾难了。

计算机访问速度分级:

表1 时间单位

以3.3GHz的CPU为例:

表2 系统的各种延时

正如你所见,CPU周期的时间非常短,这段时间,光的速度大约只能走0.5米。想象一下,是不是非常震撼?

x86-64 CPU的整数寄存器:

我们无需刻意去记住这些寄存器的名称,不同架构的寄存器的数量和名称也不一样,我们只要知道他们是cpu内部的效率极高的存储单元即可。

回到前面的例子,为什么Prog2要比Prog1快,是因为Prog2里面用DEST这个局部变量代替了*dest。DEST是一个局部变量,在汇编指令里是直接访问寄存器,而*dest则需要去访问内存cache。

2.汇编代码简介

说到汇编语言的产生,首先要讲一下机器语言。机器语言是机器指令的集合。机器指令展开来讲就是一台机器可以正确执行的命令。电子计算机的机器指令是一列二进制数字。计算机将之转变为一列高低电平,以使计算机的电子器件受到驱动,进行运算。

上面所说的计算机指的是可以执行机器指令,进行运算的机器。这是早期计算机的概念。在我们常用的PC机中,有一个芯片来完成上面所说的计算机的功能。这个芯片就是我们常说的CPU(Central Processing Unit,中央处理单元)。每一种微处理器,由于硬件设计和内部结构的不同,就需要用不同的电平脉冲来控制,使它工作。所以每一种微处理器都有自己的机器指令集,也就是机器语言。

早期的程序设计均使用机器语言。程序员们将用0, 1数字编成的程序代码打在纸带或卡片上,1打孔,0不打孔,再将程序通过纸带机或卡片机输入计算机,进行运算。这样的机器语言由纯粹的0和1构成,十分复杂,不方便阅读和修改,也容易产生错误。

程序员们很快就发现了使用机器语言带来的麻烦(何止是麻烦,简直令人发狂),它们难于辨别和记忆,给整个产业的发展带来了障碍,于是汇编语言产生了。

汇编语言的主体是汇编指令。汇编指令和机器指令的差别在于指令的表示方法上。汇编指令是机器指令便于记忆的书写格式。

我们举个例子看下:

源代码:

汇编代码和机器码:

可以看到汇编代码,好歹还有几个能猜出意思的单词。

有没有觉得现在的程序员还是挺幸福的。

  资料直通车:Linux内核源码技术学习路线+视频教程内核源码

学习直通车:Linuxc/c++高级开发【直播公开课】

零声白金VIP体验卡:零声白金VIP体验卡(含基础架构/高性能存储/golang/QT/音视频/Linux内核)

3.汇编指令简介

汇编语言是计算机语言的一种,是一种低级语言。相比高级语言,汇编语言更接近底层硬件,使用更加直接,效率更高。但相对而言,汇编语言更加复杂,语法更加严格。

  • 操作数指示符:

大多数指令有一个或多个操作数,指示出执行一个操作中要使用的源数据值。

操作数一般可以分为三类:

立即数

寄存器

内存引用

  • 数据传送指令:

最频繁使用的指令,负责将数据从一个位置复制到另一个位置。

例如:

mov %rbx, %rax : 将rbx寄存器的值移动到rax寄存器

mov %rbx, (%rax) : 将rbx寄存器的值移动到rax寄存器所表示的内存地址中

以及mov指令的一些扩展指令: movb, movw, movl, movq等等

  • 压入和弹出栈数据

将数据压入程序栈中,以及从程序栈中弹出数据。

push %rbp : 将%rbp寄存器的值压入程序栈指针指向的位置

pop %rbp : 将栈指针指向的数据弹出,放入%rbp寄存器

  • 算数和逻辑操作

  • 跳转指令

导致执行切换到程序中一个全新的位置

jmp %rax 用寄存器%rax中的值作为跳转目标

jmp是无条件跳转,还有一些条件跳转指令,有兴趣的同学可以查一下资料。

  • 比较和测试指令

比较指令CMP,只设置条件码,不更新目的寄存器,其余的行为和SUB指令一致

测试指令TEST,只设置条件码,不更新目的寄存器,其余的行为和ADD指令一致

更多详细的内容,可以通过相关书籍进行系统性的了解,鉴于篇幅,就不多介绍了。

让我们回到刚才的例子,可以看到由于把内存引用替换成了访问寄存器,程序性能就有了明显的提升。

三.帮助提高CPU分支跳转的正确率

我们还是先看一个例子:

看上面的两个函数,它们都是calloc一个全零数组x(这里不能直接用数组赋值,否则编译器会足够聪明进行自动的优化),遍历x中的每个数,如果等于0,执行分支A,否则执行分支B。

唯一的不同就是在分支判断的时候,prog2.c加了likely。我们先看下实际的结果如何:

可以看出,加了likely的prog2,明显用时变短。原因何在?

为了理解上面的例子,我们先介绍CPU流水线相关知识:

3.1. CPU流水线简介

CPU流水线是一种使用多级缓存来提高处理器性能的技术。它是指将CPU操作分为多个阶段,每个阶段单独完成一个操作,然后将结果传递给下一个阶段,以此类推。每个阶段都有一个独立的部件,并且所有部件都能同时处理不同的指令。现代CPU都会采用这种技术来提高CPU的运行效率。

CPU流水线通常包括以下五个阶段:

1)取指令(Instruction fetch):从存储器中读取指令。

2)指令译码(Instruction decode):将指令转换为可执行的指令。

3)执行指令(Instruction execute):执行指令的操作。

4)写回(Write back):将执行指令得到的结果写回内存中。

5)更新程序计数器(Update program counter):将程序计数器加1,使它指向下一个指令。

举个简单的例子:

我们假设每一个步骤执行时间都是一个时钟周期,那么一条指令执行需要3个时钟周期

CPU 执行指令的3个时钟周期里,取值单元只在第一个时钟周期里工作,其余两个时钟周期都处于空闲状态,其它两个执行单元也是如此,效率太低了。

解决方法就是引入流水线,引入流水线工作模式后可以看到,除了刚开始第一个时钟周期大家还可以偷懒外,其余的时间都不会闲着

CPU流水线的优点是可以同时执行多个指令,从而提高了处理器的效率。但它也存在一些问题,例如数据相关性(Data dependency)和控制相关性(Control dependency),这些问题可能导致流水线停滞,降低CPU的性能。

执行的程序指令如果是顺序结构,没有中断或跳转,流水线确实可以提高执行效率。但是当程序指令中存在跳转、分支结构时,下面预取的指令可能就要全部丢掉了,需要到要跳转的地方重新取指令执行。一般来说分支预测错误的处罚大约是19个时钟周期。(具体计算方法这里不做详细介绍了)。

我们看下前面提到的例子汇编出来的结果:

prog2,这里汇编是”jne”,意思是如果判断结果不为0,就跳转到地址 800 的地方执行。我们知道这里的判断一直是0。所以,cpu指令顺序向下执行,并不会发生预判错误,预取的指令也不会丢弃。这样就不会遭到分支预测错误的惩罚,效率会提高。

所以有些情况下,当我们根据实际的情况可以判断出哪条分支的可能性更高的时候,我们就可以站在上帝视角给予一定的提示,这样就可以降低分支预测错误,减少CPU的无用功了,从而可以有效的提高性能,同时也节省了功耗。

四.总结

从计算机系统的角度来看,性能优化的几个思考方向:

1. 尽可能高效的获取数据

2. 尽可能减少CPU的无用功。

3. 尽可能在有限的时间内,让CPU干更多的有效的事情。

4. 完成同样的工作任务,尽可能的让CPU少做事。

原文作者:内核工匠

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

2023年12月03日新闻简报(国内国际)

新闻简报 每天三分钟,朝闻天下事。今天是:2023年12月03日,星期日,农历十月廿一,祝工作愉快,身体健康,生活喜乐: 👉👉国内新闻 1、1日凌晨,四川…

docker-速通

1.命令-镜像操作 docker pull nginx #下载最新版 docker pull nginx:1.20.1 #下载指定版本 镜像名:版本名(标签) docker images #查看所有镜像 # 如果只写镜像名实际就是redis redis:latest 记住这个不是命令 docker rmi 镜像名:版本号/镜像id…

Pandas教程06:DataFrame.merge数据的合并处理

DataFrame.merge() 是 pandas 库中用于合并两个DataFrame数据的方法。该方法主要用于根据一个或多个键(键可以是列名或索引)将两个 DataFrame 连接在一起,这个过程类似于 SQL 中的 JOIN 操作。 #我的Python教程 #微信公众号:wdPy…

【PTA-C语言】实验四-循环结构II

如果代码存在问题,麻烦大家指正 ~ ~有帮助麻烦点个赞 ~ ~ 实验四-循环结构II 7-1 跟奥巴马一起画方块(分数 15)7-2 打印九九口诀表(分数 10)7-3 求符合给定条件的整数集(分数 15)7-4 求特殊方程…

网络虚拟化场景下网络包的发送过程

网络虚拟化有和存储虚拟化类似的地方,例如,它们都是基于 virtio 的,因而在看网络虚拟化的过程中,会看到和存储虚拟化很像的数据结构和原理。但是,网络虚拟化也有自己的特殊性。例如,存储虚拟化是将宿主机上…

爬虫学习-基础(HTTP原理)

目录 一、URL和URI 二、HTTP和HTTPS (1)HTTP (2)HTTPS (3)HTTP与HTTPS区别 (4)HTTPS对HTTP的改进:双问的身份认证 三、TCP协议 (1)TCP三次握手…

2000-2021年上市公司过度负债数据

2000-2021年上市公司过度负债数据 1、时间:2000-2021年 2、指标: 证券代码、证券简称、会计期间、上市日期、行业代码、行业名称、是否剔除ST或*ST股、是否剔除当年新上市、已经退市或被暂停退市的公司、产权性质、盈利能力、杠杆率行业中位数、成长性…

ELK高级搜索,深度详解ElasticStack技术栈-下篇

前言:ELK高级搜索,深度详解ElasticStack技术栈-上篇 14. search搜索入门 14.1. 搜索语法入门 14.1.1 query string search 无条件搜索所有 GET /book/_search结果: {"took" : 969,"timed_out" : false,"_shar…

架构图是什么,怎么做?

架构图是一种用来描述系统或软件的结构和组成的图形表示。它展示了系统中各个组件之间的关系、交互和功能。通过绘制架构图,可以更好地理解和沟通系统的设计和实现。 绘制架构图的软件 目前市场上有许多用于绘制架构图的软件工具,下面简单…

Conmi的正确答案——“xxx.sh: 行 2: $‘\r‘: 未找到命令”

Ubuntu版本:23.10(桌面版) 问题原因: 这个sh文件被window编辑后会以DOS格式保存,但linux格式中回车只认“\n”,而DOS格式的回车则是“\r\n”。 解决方案: 使用nano打开一次文件,并且…

有两个篮子,分别为A 和 B,篮子A里装有鸡蛋,篮子B里装有苹 果,请用面向对象的思想实现两个篮子里的物品交换

问题: 有两个篮子,分别为A 和 B,篮子A里装有鸡蛋,篮子B里装有苹 果,请用面向对象的思想实现两个篮子里的物品交换 代码 package cn.ljh.algorithmic;/*** author JH*/ public class Demo07 {public static void main…

git rebase冲突说明(base\remote\local概念说明)

主线日志及修改 $ git log master -p commit 31213fad6150b9899c7e6b27b245aaa69d2fdcff (master) Author: Date: Tue Nov 28 10:19:53 2023 08004diff --git a/123.txt b/123.txt index 294d779..a712711 100644 --- a/123.txtb/123.txt-1,3 1,4 123 4^Mcommit a77b518156…

使用SpringBoot和ZXing实现二维码生成与解析

一、ZXing简介 ZXing是一个开源的,用Java实现的多种格式的1D/2D条码图像处理库。它包含了用于解析多种格式的1D/2D条形码的工具类,目标是能够对QR编码,Data Matrix, UPC的1D条形码进行解码。在二维码编制上,ZXing巧妙地利用构成计…

【Linux】命令行参数

文章目录 前言一、C语言main函数的参数二、环境变量总结 前言 我们在Linux命令行输入命令的时候,一般都会跟上一些参数选项,比如l命令,ls -a -l。以前我总是觉得这是理所当然的,没深究其本质究竟是什么,今天才终于知道…

180天Java从小白到就业-Day03-03Java位运算符、赋值运算符、数据交换的三种方式

1. 位运算符 Q:为什么要学习位运算 A:由于其运算效率更高,在JDK源码(例如ArrayList、HashMap)中大量使用位运算,想要看懂JDK源码必须懂位预算,但是在公司开发业务系统时位运算使用并不多。 Q…

N-135基于springboot,vue高校图书馆管理系统

开发工具:IDEA 服务器:Tomcat9.0, jdk1.8 项目构建:maven 数据库:mysql5.7 系统分前后台,项目采用前后端分离 前端技术:vueelementUI 服务端技术:springbootmybatisredis 本项…

深入理解同源限制:网络安全的守护者(上)

🤍 前端开发工程师(主业)、技术博主(副业)、已过CET6 🍨 阿珊和她的猫_CSDN个人主页 🕠 牛客高级专题作者、在牛客打造高质量专栏《前端面试必备》 🍚 蓝桥云课签约作者、已在蓝桥云…

【可视化】图像分割中标签label与预测mask的两种可视化方法

前两天进行一个分割项目,模型训练完成后需要对分割效果可视化,特此记录两种方法。 示例中用到的原图、label和mask如下所示: 1. 标签label填充,预测mask勾线 第一种是标签label填充样覆盖,预测mask采用勾线描边的方式…

Oracle ORA12514 监听程序当前无法识别连接描述符中请求的服务

最简单的有可能是你的服务还没有开启,需要启动服务!!!! 在连接数据库的时候,有时会遇到一个“ORA12514:监听程序当前无法识别连接描述符中请求的服务”的错误,这个错误其实就是数据…

网页中的json文档,怎么保存到本地

火狐浏览器操作方法 方法一 右键——>另存页面为 方法二 点击右边的三条横线——>另存页面为 谷歌浏览器操作方法 方法一 右键——>另存为 方法二