大家好我是jiantaoyab,这是我参考网上很多博主写的文章,所总结作为学习的笔记,在这里分享给大家,还有一些书籍《深入理解计算机系统》《计算机组成:结构化方法》《计算机体系结构:量化研究方法》
什么是性能呢?
在日常的生活中,我们常常提到性能,买手机或者是电脑,我们都想在合适的价格下买到性能最好的,那究竟什么是性能呢?
性能是时间的倒数
计算机的性能,其实和我们干体力劳动很像,好比是我们要搬东西。对于计算机的性能,我们需要有个标准来衡量。这个标准中主要有两个指标。
- 响应时间(Response time)或者叫执行时间(Execution time)。想要提升响应时间这个性能指标,你可以理解为让计算机“跑得更快”
- 吞吐率(Throughput)或者带宽(Bandwidth),想要提升这个指标,你可以理解为让计算机“搬得更多”
所以,响应时间指的就是,我们执行一个程序,到底需要花多少时间。花的时间越少,自然性能就越好。而吞吐率是指我们在一定的时间范围内,到底能处理多少事情。这里的“事情”,在计算机里就是处理的数据或者执行的程序指令。
和搬东西来做对比,如果我们的响应时间短,跑得快,我们可以来回多跑几趟多搬几趟。所以说,缩短程序的响应时间,一般来说都会提升吞吐率。
当然我们还可以多找一些人来搬东西,于是就有了8核,16核的出现。人多力量大,同时处理数据,在单位时间内就可以处理更多数据,吞吐率自然也就上去了
一般的把性能定义成响应时间的倒数
性能 = 1 / 响应时间 性能 = 1/ 响应时间 性能=1/响应时间
这样一来,我们只要缩短响应的时间,行能的数值就越大
计算机的计时单位:CPU 时钟
现实的时间中,我们可以用时间来衡量性能的指标,但是在计算机中,我们每次运行用一个程序的时间都是不相同的,为什么会不同呢?
在计算机中,们统计时间是用类似于“掐秒表”一样,记录程序运行结束的时间减去程序开始运行的时间。这个时间也叫 Wall Clock Time 或者 Elapsed Time,就是在运行程序期间,挂在墙上的钟走掉的时间。
但是我们的计算机是会同时运行着多个程序,CPU是在不同的程序间去切换,不过速度太快我们肉眼看不出,CPU每次切换就会跑掉一部分的时间,并且有些程序在运行的时候,可能要从网络、硬盘去读取数据,要等网络和硬盘把数据读出来,给到内存和 CPU。所以说,要想准确统计某个程序运行时间,进而去比较两个程序的实际性能,我不能用时间作为指标
在Linux下,我们能用运行time 命令。它会返回三个值,第一是realtime,也就是我们说的 Wall Clock Time,也就是运行程序整个过程中流逝掉的时间;第二个是user time,也就是 CPU 在运行你的程序,在用户态运行指令的时间;第三个是sys time,是 CPU 在运行你的程序,在操作系统内核里运行指令的时间。而程序实际花费的 CPU 执行时间(CPU Time),就是 user time 加上 sys time。
实际上用的时间是0.015s,但是CPU time 是 0.020 + 0.007 = 0.027 s ,为什么我这里的时间比real还多了呢?
通过top命令可以看到我的 seq 和 wc 命令占据几乎是CPU 100%,2个命令给分配到了不同的CPU中执行,于是user 和 sys 和时间 是2个CPU运行的时间这和,就可能超过了real 的时间了
但是即使我们已经拿到了 CPU 时间,我们也不一定可以直接“比较”出两个程序的性能差异,除了 CPU 之外,时间这个性能指标还会受到主板、内存这些其他相关硬件的影响。所以,我们需要对“时间”这个我们可以感知的指标进行拆解,把程序的 CPU 执行时间变成 CPU 时钟周期数(CPU Cycles)和 时钟周期时间(Clock Cycle)的乘积。
程序的 C P U 执行时间 = C P U 时钟周期数 × 时钟周期时间 程序的 CPU 执行时间 =CPU 时钟周期数×时钟周期时间 程序的CPU执行时间=CPU时钟周期数×时钟周期时间
时钟周期时间
我们在买CPU的时候,会看到CPU会标明是多少多少Hz,这里的 Hz 就是电脑的主频(Frequency/Clock Rate)。假如是 3.0GHz,我们可以先粗浅地认为,CPU 在 1 秒时间内,可以执行的简单指令的数量是 3.0G 条。
这个3.0GHz就是CPU钟表能识别出来最小的时间间隔像我们挂在墙上的挂钟,都是“滴答滴答”一秒一秒地走,所以通过墙上的挂钟能够识别出来的最小时间单位就是秒。主频越高,意味着这个表走得越快
而在 CPU 内部,和我们平时戴的电子石英表类似,有一个叫晶体振荡器(Oscillator Crystal)的东西,简称为晶振。我们把晶振当成 CPU 内部的电子表来使用。晶振带来的每一次“滴答”,就是时钟周期时间。
CPU 时钟周期数
对于 CPU 时钟周期数,我们可以再做一个分解,把它变成“指令数×每条指令的平均时钟周期数(Cycles Per Instruction,简称 CPI)不同的指令需要的 Cycles 是不同的,加法和乘法都对应着一条 CPU 指令,但是乘法需要的 Cycles 就比加法要多,自然也就慢。在这样拆分了之后,我们的程序的 CPU 执行时间就可以变成这样三个部分的乘积。
程序的 C P U 执行时间 = 指令数 × C P I × C l o c k C y c l e T i m e 程序的 CPU 执行时间 = 指令数×CPI×Clock Cycle Time 程序的CPU执行时间=指令数×CPI×ClockCycleTime
每条指令的平均时钟周期数 CPI,就是一条指令到底需要多少 CPU Cycle,指令数,代表执行我们的程序到底需要多少条指令、用哪些指令
光堆主频就能无限制的提升CPU吗?
奔腾4的失败就告诉我们,是不可以的,我们还得考虑功耗的问题
CPU,一般都被叫作超大规模集成电路(Very-Large-Scale Integration,VLSI)。这些电路,实际上都是一个个晶体管组合而成的。CPU 在计算,其实就是让晶体管里面的“开关”不断地去“打开”和“关闭”,来组合完成各种运算和功能。
想要计算得快,一方面,我们要在 CPU 里,同样的面积里面,多放一些晶体管(增加晶体管可以增加硬件能够支持的指令数量),也就是增加密度;另一方面,我们要让晶体管“打开”和“关闭”得更快一点,也就是提升主频。而这两者,都会增加功耗,带来耗电和散热的问题
为什么不能选择增加CPU的面积呢?
如果增加CPU的面积的话,晶体管的距离就变大了,电信号传输的时间就会变长
CPU的功率
功耗 = 1 / 2 × 负载电容 × 电压的平方 × 开关频率 × 晶体管数量 功耗 ~= 1/2 ×负载电容×电压的平方×开关频率×晶体管数量 功耗 =1/2×负载电容×电压的平方×开关频率×晶体管数量
为了要提升性能,我们需要不断地增加晶体管数量。同样的面积下,我们想要多放一点晶体管,就要把晶体管造得小一点。这个就是平时我们所说的提升“制程”。从 28nm 到 7nm,相当于晶体管本身变成了原来的 1/4 大小。这个就相当于我们在工厂里,同样的活儿,我们要找瘦小一点的工人,这样一个工厂里面就可以多一些人。我们还要提升主频,让开关的频率变快,也就是要找手脚更快的工人
但是,功耗增加太多,就会导致 CPU 散热跟不上,这时,我们就需要降低电压。这里有一点非常关键,在整个功耗的公式里面,功耗和电压的平方是成正比的。这意味着电压下降到原来的 1/5,整个的功耗会变成原来的 1/25
通过并行提高性能
提升响应时间,就好比提升你用的交通工具的速度,比如原本你是开汽车,现在变成了火车乃至飞机。本来开车从广东到北京要 很多 个小时,换成飞机就只要 几个小时了,但是,在此之上,再想要提升速度就不太容易了。我们的 CPU 在奔腾 4 的年代,就好比已经到了飞机这个速度极限。
假如我们可以一次同时开 2 架、4 架乃至 8 架飞机,这就好像我们现在用的 2 核、4 核,乃至 8 核的 CPU,虽然时间没变,但是我们能同时运输更多的东西。
但是,并不是所有问题,都可以通过并行提高性能来解决。如果想要使用这种思想,需要满足这样几个条件。
需要进行的计算,本身可以分解成几个可以并行的任务
需要能够分解好问题,并确保几个人的结果能够汇总到一起
在“汇总”这个阶段,是没有办法并行进行的,还是得顺序执行,一步一步来。
这就引出了性能优化中,常常用到的一个经验定律,阿姆尔定律,这定律说的就是,对于一个程序进行优化之后,处理器并行运算之后效率提升的情况
阿姆达尔定律
优化后的执行时间 = 受优化影响的执行时间 / 加速倍数 + 不受影响的执行时间 优化后的执行时间 = 受优化影响的执行时间 / 加速倍数 + 不受影响的执行时间 优化后的执行时间=受优化影响的执行时间/加速倍数+不受影响的执行时间
提高性能可以优化的方面
- 加速大概率事件
在数学中大概率就是一个随机事件发生的概率很大,在一次实验中很可能发生,举个在计算机中的例子,假如一段代码有多个分支,每个分支都有一个被执行到的概率,因此我们可以实验分析每个分支的概率,按照概率大小进行排序,对大概率的分支优先加速优化。
- 通过流水线提高性能
流水线是并行性的一个特例,处理器就是通过流水线的方式来提高指令的执行效率,流水线最初起源于车辆制造,车辆被拆分成很多个的部件,每个部件又可以继续拆分,拆分后的所有部件形成了流水线图,流水线图呈拓扑结构,拓扑中同一层的部件可以由独立的生产线进行生产,这样就可以灵活地扩充生产线来提高车辆的制造效率。
流水线优化的最大目标是将依赖延迟到最后,对所有的依赖关系进行拆解,拆解成多个不互相依赖的步骤,使得每个步骤可以并行运行,从而从整体上提高运行吞吐量和效率。
- 通过预测提高性能
在某些情况下,我们可以大胆地预测计算机下一步的动作,可以提前将这些动作完成,等到需要执行这些动作的时候,已经提前准备好了,当然提前预测也是有失败率的,假设失败造成的效率下降为X,执行了预测的动作后,提高的效率为Y,只要X小于Y,这个提前预测就是有效的,是可以提高性能的
计算机里一个比较常见的提前预测的例子就是磁盘预读,通常我们读取磁盘的数据时,会先将硬盘数据读入到缓冲中,程序从缓冲中读取数据,当缓冲数据被读完后,处理器再从磁盘读取下一批数据,这里可以假设读取磁盘数据是顺序的,我们可以提前把下一次要读取的数据,加入到另外一个缓冲中,这样读取当前缓冲数据的时候,已经开始读取下一批缓冲的数据了,充分地利用了硬件资源,当前缓冲数据读取完毕后,直接可以用下一批缓冲的数据,不需要等待,当然这里如果下一批数据变成了随机的,那就可能会造成已经读取的数据的失效和浪费,不过,如果大部分情况下都是顺序读取的,提前读取获得的性能提高远大于由于读取失效造成的性能下降