You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

146 lines
12 KiB
Markdown

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

# 19 | 性能优化六大原则:三要三不要,快速有效地进行优化
你好,我是庄振运。
今天我们进入了专栏的新模块性能优化。在这个模块里我会先从“性能优化的六大原则”开始讲起然后再为你讲解实践中普遍采用的十个性能优化策略并且分别针对CPU、系统、存储以及跨层这几个领域讲讲具体的优化案例。
我们今天先探讨性能优化的原则。在讲具体原则之前,我想先给你讲一个有趣的往事。
我曾经负责过一个存储服务的性能优化和容量效率。那个服务的容量需求很大但它的最大性能瓶颈不是CPU而是存储的空间。
所以虽然有很多人给我们各种建议让我们花时间做CPU优化我都尽量挡了回去。因为我知道CPU不是最大问题所以坚持不懈地通过各种途径优化数据大小甚至以牺牲CPU为代价。最后的结果很好大幅度地降低了那个服务的容量需求。
在这个性能优化的场景,我们遵循了一个原则,那就是**优先优化最大性能瓶颈**。这其实就是马上要讲到的“三要”原则中的第一个。
## 性能优化的原则概述
在实际的性能优化中,我们需要考虑的因素很多,也经常需要在多个角度和目标间做一些平衡和取舍。为了帮助你把握这些,我个人总结出了六条原则,我把它们概括为:“**三要,三不要**”。
![](https://static001.geekbang.org/resource/image/bc/bb/bc08d73063d641b231db155d7ffe26bb.jpg)
* 三个“要”原则是:要优先查最大的性能瓶颈,性能分析要确诊性能问题的根因,性能优化要考虑各种的情况。
* 三个“不要”的原则是:不要做过度的、反常态的优化,不要过早做不成熟的优化,不要做表面的肤浅优化。
接下来,我们一个个地讲一下这六个优化原则。
## 三个“要”的原则
你可能已经发现了,这三个“要”的原则之间,其实是步步递进的关系。也就是首先需要查找最大性能瓶颈,然后确诊性能瓶颈产生的原因,最后针锋相对地提出最好的解决方案。
而这个最优解,往往是在考虑各种情况之后提出来,并最终被选中的。
### 要优先查最大的性能瓶颈
任何一个应用程序或者系统,总会有很多地方可以优化。
可是你知道要从何处下手吗?
其实你可以参考古人的观点,唐代的诗人杜甫说过:“射人先射马,擒贼先擒王”。会打仗的将军,一定会从“最值得打的地方”开始,性能优化工作也是如此。
我们永远都要优先从**最大的性能瓶颈**入手。
一般来讲,如果找到最大的性能瓶颈,并且解决了它,那这个系统的性能会得到最大的提升(参见[第4讲](https://time.geekbang.org/column/article/174462)帕累托法则)。反之,如果不解决最大的性能瓶颈,反而退而求其次,去解决了其他的性能问题,整个系统的性能或许会更高一些,但是提升的程度往往是非常有限的。
比如一个应用程序的最大性能瓶颈是CPU的使用率太高而其他种类型的资源比如网络和存储都很有富余。这种情况下如果你去优化网络和存储方面显然是不能大幅度地提升整体性能的。
不过这里有个稍微特殊的情况,就是**内存**。因为内存的分配和回收也会消耗一些CPU资源如果这时去优化内存使用很多时候的确会帮助你降低CPU使用率不过降低的幅度一定是很有限的。
归根结底要降低CPU的使用率**最有效的方法是做性能分析和剖析找出程序中使用CPU最多的地方**,然后对症下药地做优化。
### 要确诊性能问题的根因
我们前面讲过程序和系统如果在某个地方有性能瓶颈肯定是这个地方的资源不够用了不管是CPU、内存还是网络参见[第14讲](https://time.geekbang.org/column/article/182915))。
所以,当我们确定了最大的性能瓶颈后,就需要对这一性能瓶颈做彻底的性能分析,找出资源不够使用的原因,也就是考察使用资源的地方。
一种资源被使用的地方往往有好几个,我们需要一个一个地去分析考虑。只有彻底分析了各种使用的情况,才能进一步找出最主要的,也是最可能优化的原因,对症下药。
有些资源使用的原因也许是完全合理的。对这些合理的使用,有些或许已经仔细优化过了,很难再做优化。而另外一些则有可能继续优化。对资源的不合理使用,我们就要尽量想办法去掉。
对于需要优化的地方,我们就需要进一步考虑**优化工作的投入产出比例**,就是**既考虑成本,也考虑带来的好处**。因为有些情况下,虽然你可以去优化,但获得的收益并不大,所以不值得去做。
另外要提醒你的是,确诊性能问题的原因有时候非常困难,需要做多方面的性能测试、假设分析并验证。
比如CPU的使用有操作系统的原因有应用程序的原因但也有些CPU的问题是在非常边缘的场景下才发生的。为了暴露问题我们经常需要创造特殊的场景来重现遇到的性能问题。
### 要考虑各种情况下的性能
性能问题确诊原因后,我们就可以进入下一步,找解决方案了。一般说来,找一个解决方案并不难,甚至找好几个方案也不难;但是找出一个好的、最优的解决方案是真心不容易。
为什么这么说呢?
因为实际生产环境很复杂,而且会出现各种各样的特殊场景。
**针对某个具体场景提出的一个解决方案,多半并不能适应所有的场景。**
所以,对提出的各种方案进行评估时,我们必须考虑各种情况下这个方案可能的表现。如果一个方案在某些情况下会导致其他严重的问题,这个方案或许就不是一个好的方案。
但同时你也需要意识到任何解决方案都有长短有Tradeoff。如果苛求一个能在所有场景下都最优的解决方案往往是缘木求鱼是不现实的。
比如一种优化方案,可以让平均响应时间最小,但高百分位比较高。另外一种优化方案正好相反。那我们就需要考虑对自己来说哪种指标更重要。也就是说,我们经常需要在**不同性能指标间权衡**,以找到一个最优解能**达到总体和整体最优**。
这就需要我们**有一个整体的意识和判断**。
或许一个方案并不能面面俱到,在有些场景下性能不好。但是不同场景的出现概率不同,对其它模块造成的影响也不一样,并且最终客户的体验也不尽相同。这些因素都要考虑到取舍的决策过程中。
综合以上因素,在实际的优化过程中,我们经常会**反复权衡利弊和取舍来做最终决定**。
## 三个“不要”的原则
讲完了三个“要”的原则,我们接着来看三个“不要”的原则。
### 不要过度地反常态优化
性能优化的目标,是追求**最合适的性价比**或**最高的投入产出比**,在满足要求的情况下,尽量不要做过度的优化。过度的优化会增加系统复杂度和维护成本,使得开发和测试周期变长。虽然性能上带来了一定程度的提升,但是和导致的缺点来比,孰轻孰重尚不可知,需要仔细斟酌,衡量得失。
我的建议是,**根据产品的性能要求来决策**。
在设计产品时,我们对产品的性能会有一定的要求,比如吞吐量,或者客户响应时间要达到多少多少。如果达不到这个既定指标,就需要去优化。反之,如果能满足这些指标,那么就不必要花费太多时间精力去优化。
比如,我们要设计一个内部查询系统,预计最多只有一百个人同时在线使用的话,就完全不用按照百万在线用户的目标去过度优化。
更重要的是,多数的优化方法是并不是完美无缺的,是有缺点的,尤其是可能会对系统设计的简化性,对代码的可读性和可维护性有副作用。如果系统简化性和代码可读性更加重要,当然就更不能过度优化。
### 不要过早的不成熟优化
要体会这一原则我们先引用著名计算机科学家高德纳Donald Knuth的一段话“现实中的最大问题是程序员往往花太多时间来在错误的地方和错误的时间来试图提高效率和性能。过早的优化是编程中所有邪恶和悲剧或至少是大多数邪恶和悲剧的根源。”
![](https://static001.geekbang.org/resource/image/69/a7/6964528af8b4212dfa2291749b7415a7.png)
你只要稍微思考一下高德纳的话,就会发现,这句话在很多场景下都是很有道理的。
比如,在敏捷开发过程中,尤其是在面对一个全新的产品时,在业界没有先例和经验可遵循的情况下,最看重的特点是快速的迭代与试错,“尽快推出产品”是最重要的。这时,过早的优化很可能优化错地方,也就是优化的地方并非真正的性能瓶颈,因此让“优化工作”成为了无用功。而且,越早的优化就越容易造成负面影响,比如影响代码的可读性和维护性。
我个人认为,**如果一个产品已经在业界很成熟**,大家非常清楚它的生产环境特点和性能瓶颈,那么**优化的重要性可以适当提高**。否则的话,在没有实际数据指标的基础上,为了一点点的性能提升而进行盲目优化,的确是得不偿失的。
### 不要表面的肤浅优化
性能优化很忌讳表面和肤浅的优化,也就是那种“头痛医头,脚痛医脚”的所谓“优化”。如果对一个程序和服务没有全局的把握,没有理解底层运行机制,任何优化方案都很难达到最好的优化效果。
比如如果你发现一个应用程序的CPU使用率并不高但是吞吐率上不去表面的优化方式可能是增大线程池来提升CPU使用率。这样的简单“优化”或许当时能马上看到效果比如吞吐率也上去了但是如果你仔细想想就会发现如此的表面优化非常有问题。
这样的情况下,线程池开多大最合适?需不需要根据底层硬件和上层请求的变化而对线程池的大小调优呢?如果需要,那么手工调整线程池大小就是一个典型的“头痛医头”的优化。
为什么呢?
因为部署环境不会一成不变比如以后CPU升级了核数变多了你怎么办再次手工去调整吗这样做很快会让人疲于奔命难以应付并且很容易出错。
对这样的场景正确的优化方式是彻底了解线程的特性以优化线程为主。至于线程池的大小最好能够自动调整。千万别动不动就手工调优。如果这样手工调整的参数多了就会做出一个有很多可调参数的复杂系统很难用也很难调优很不可取。就比如我们都熟悉的JVM调优有上千个可调参数非常被人诟病。
## 总结
唐朝的名相魏征说过:“求木之长者,必固其根本”。意思是说,如果要一棵树长得高,必须让它的根牢固。否则的话,正如魏征自己所说:“根不固而求木之长”,一定是“知其不可”。根基不牢固,就不可能长成参天大树。
![](https://static001.geekbang.org/resource/image/b0/e9/b07a06f7da4768d7398e1a174326cce9.png)
同理,对现代互联网的服务和系统来说,性能问题是根本的问题。如果不知道系统的性能瓶颈,查不出性能根因,不知道如何解决,无法做合理的优化,这个服务和系统一定不会高效。
这一讲我们总结了六个性能优化的原则,这些原则的终极目的,就是找出性能的最大瓶颈,查出根因,并作做相应的最优优化,从而让我们的系统这棵树长高、长大。
## 思考题
* 今天介绍的六大原则里面,你认为哪个原则最重要?为什么?
* 你过去的工作中有没有因为没有遵循这几个原则而吃后悔药的时候比如你在选择一个数据结构的具体实现时比如Set有没有考虑各种场景下的性能
欢迎你在留言区分享自己的思考,与我和其他同学一起讨论,也欢迎你把文章分享给自己的朋友。