1 计算机时间尺度
计算机世界与我们人类直观世界有巨大的差异。这种差异表现在多方面,其中之一就是时间尺度。
在计算机世界,时间的常用单位是纳秒、微妙、毫秒等。而在直观世界,我们通常使用(或者能够切身感受到的)时间单位是秒、分、时等。
这两种客观存在的截然不同的时间尺度,对我们人类理解计算机的工作特点是不利的。为了更好描述这一实际情况,Brendan Gregg在《Systems Performance: Enterprise and the Cloud》一书中,制作了一张计算机时间与人类直观时间的映射关系表。
具体来说,如果将计算机的CPU周期作为最小单位,并标定为1秒。那么,可以得到以下表格。
任务类型 | 实际时间 | 等价时间 |
---|---|---|
CPU周期 | 0.3纳秒 | 1 秒 |
1级缓存访问 | 0.9纳秒 | 3秒 |
2级缓存访问 | 2.8纳秒 | 9秒 |
3级缓存访问 | 12.9纳秒 | 43秒 |
内存访问 | 120纳秒 | 6分 |
固态硬盘访问 | 50~150微妙 | 12~6天 |
普通硬盘访问 | 1~10毫秒 | 1~12个月 |
网络访问:从洛杉矶到纽约 | 40毫秒 | 4年 |
网络访问:从洛杉矶到英国 | 81毫秒 | 8年 |
网络访问:从洛杉矶到澳洲 | 183毫秒 | 19年 |
热重启(或软重启) | 4秒 | 423年 |
冷重启(或硬重启) | 40秒 | 4000年 |
初次看到这张表格,我们感受到了不小的震撼。这种震撼表现在两个“数量级”上。
-
计算机世界的时间单位非常微观,与人类直观时间单位要小多个数量级。如果计算机和人类一样慢,那么计算机完成任务的速度会慢到不可想象:假设CPU周期是1秒,重启计算机将需要4000年!
-
计算机内部,不同类型的操作所消耗的时间通常相差多个数量级。例如,访问硬盘的单位时间相比访问内存的单位时间可能要高几个数量级;访问远程网络的单位时间相比访问硬盘的单位时间,可能又要高几个数量级。
需要注意的是,上述表格基于的是时延(latency)的角度。实际上,对于单次操作来说,其时耗除了取决于时延,还取决于带宽(bandwidth)。那么,二者的区别何在?
所谓时延,就是从发起操作请求,到请求被响应的时间;而所谓带宽,则是从开始响应请求起,处理响应的速度(speed)。例如,如果访问硬盘的时延是1毫秒,而硬盘的读写速度是100Mb/s,那么从硬盘读取0.1Mb大小的文件,所消耗的总时间是1毫秒+1毫秒=2毫秒。
对于存储器件来说,访问时延与读写速度通常是强相关的。例如,硬盘的访问时延,相比内存的访问时延,通常要高几个数量级;类似的,硬盘读写速度,相比内存读写速度,往往也要低几个数量级。
2 计算机程序类型
计算机是个复杂的系统。在这里,我们简单粗暴地将计算机划分为两部分:中央处理单元CPU,和CPU的外围子系统(内存、磁盘、网络等)。
在计算机工作过程中,如果CPU和外围子系统没有同时在工作,那么,就有两种可能:要么是CPU在等待外围子系统,要么是外围子系统在等待CPU。
从上文的表格中,我们看可以看到,CPU的速度是最快的,而外围子系统则不同程度地比CPU慢。由于CPU速度极快,外围子系统相对较慢,在许多情况下,CPU是空闲的,处于等待外围请求的状态。但是,有些时候,CPU非常忙碌,以至于不能及时处理来自外围子系统的请求。这时,就反过来了,是外围子系统在等待CPU。
计算机是用来执行程序的。计算机程序的类型多种多样。对于程序来说,CPU、内存、磁盘、网络等都是供其使用的资源。不同的程序,通常都需要使用各种资源,但是重点使用的资源则有所不同。基于这一特点,可以把计算机程序划分为以下类型。
-
CPU密集型:程序的执行时间,主要取决于CPU的处理能力;CPU的能力越强,程序就执行得越快。通俗的说,就是那些非常消耗CPU的程序。
-
内存密集型:程序的执行时间,主要取决于内存的容量;内存容量越大,程序就执行得越快。通俗的说,就是那些非常消耗内存的程序。
-
I/O密集型:程序的执行时间,主要取决于访问和读写I/O(Input/Output)的效率。I/O访问越快,读写越快,程序就执行得越快。通俗的说,就是那些需要频繁进行I/O操作的程序。
3 计算机程序性能瓶颈
程序的性能,符合“木桶原理”。所谓程序的性能短板,即瓶颈,指的是程序中存在某个模块,其运行速度赶不上其余模块的脚步,从而拖累整个程序的性能。
程序的性能瓶颈可能有许多类型,常见的瓶颈有:
-
CPU瓶颈:当CPU非常繁忙,以至于它不能够及时地响应外围请求时,就出现了CPU瓶颈。此时,CPU的使用率非常高(超过80%),并且待处理的队列很长。
-
内存瓶颈: 系统没有足够大,或者足够快的内存。在这种情况下,内存为CPU提供服务的速度下降,从而影响整体的性能。当系统没有足够内存的时候,计算机会将存储转移到慢几个数量级的硬盘上。由于大部分时间CPU可能处于等待内存的状态,因此内存成为瓶颈时CPU的使用率可能处于较低的水平。
-
网络瓶颈:当通信的两个设备之间的带宽不高,或者某一端处理网络访问请求的能力不行时,存在网络瓶颈。
-
磁盘瓶颈:在计算机内部,硬盘一般是很慢的器件。即使最快的硬盘,也存在物理上的速度限制。如果程序需要高频地访问硬盘,那么磁盘将可能成为性能瓶颈。
-
程序瓶颈:有时候,系统的瓶颈是来源于程序本身的。也就是说,不是系统的资源不够,而是程序没有高效地利用这些资源。比如,现代计算机通常是多核的,而有些程序可能只利用了其中一个核;又比如,程序没有释放未使用的内存,那么随着程序的长时间运行,再大空间的内存也会被消耗殆尽(即所谓的内存泄漏)。
4 计算机程序性能优化
关于性能优化,最著名的就是Donald Knuth“过早的优化是万恶之源”的观点。意思是要避免不成熟的优化。在软件开发阶段,编程的首要目标是满足软件设计(功能)的要求,而不是满足好的性能要求。程序真正的性能瓶颈在哪里?这是很难提前准确判断的。很多时候,真正的瓶颈往往在出乎意料的地方。
“避免不成熟的优化”的另一面就是,“优化真正需要优化的代码”。程序对计算机资源的使用,符合著名的幂律分布(俗称二八定律)。即,百分之八十的计算机资源是被百分之二十的程序占用的。实际比例往往更夸张。
另一个角度。从时间上看,程序对系统资源的使用是动态的。根据阿姆达尔定律,系统中采用更快执行方式所能获得的系统性能改进程度,取决于这种执行方式被使用的频率,或所占总执行时间的比例。也就是说,真正需要优化的,并不一定是程序中执行速度最慢的模块,而是在整个程序运行过程中长时间被执行的模块。例如,在CPU密集型程序中,尽管CPU仍然是最快的(比I/O快N个数量级),但是CPU依然更可能是主要的瓶颈。此时,优化CPU,显然能够比优化I/O带来更好的性能提升。
总而言之,在性能优化之前,我们需要找到真正的瓶颈。要想找到真正的瓶颈,我们务必要先进行性能分析。要进行性能分析,需要有充分的、反映真实运行情况的数据。要获取这些数据,往往需要专业的性能分析辅助工具(Profiler)。因此,一个完整的性能优化过程应该是这样的:
1,选择性能分析工具
2,采集性能数据
3,分析性能瓶颈
4,解决性能瓶颈
5,重复2-4
5 总结
本文简要介绍了一些计算机程序性能方面的知识。程序性能是一个庞大的话题,这里仅仅从微小的角度作了浅显的解读。例如,本文的讨论只针对单主机系统,对当今业界流行的分布式系统的性能则没有触及。我们将关于程序性能的更多话题留在未来讨论。
我是肖哥shelwin,一个高质量软件工程实践者和推动者。欢迎扫描下方二维码,添加我的个人公众号测试不将就,获得更多自动化测试, 持续集成, 软件工程实践, Python编程等领域原创文章。