12-套路篇:CPU性能优化的几个思路





性能优化方法论

通过各种性能分析方法,终于找到了引发性能问题的瓶颈后,是不是立刻就要开始优化了呢?
动手之前,可以先看看下面这三个问题
1.既然要做性能优化,那要怎么判断它是不是有效呢?特别是优化后,到底能提升多少性能呢?
2.性能问题通常不是独立的,如果有多个性能问题同时发生,应该先优化哪一个呢?
3.提升性能的方法并不是唯一的,当有多种方法可以选择时,是不是总选那个最大程度提升性能的方法就行了呢?


举例
前面不可中断进程案例中,通过性能分析,发现是因为一个进程的直接I/O 
导致了iowait高达90%。那是不是用“直接I/O换成缓存I/O”的方法,就可以立即优化了呢?
1.直接I/O换成缓存I/O,可以把iowait从90%降到接近0,性能提升很明显
2.没有发现其他性能问题,直接I/O是唯一的性能瓶颈,所以不用挑选优化对象
3.缓存I/O是目前用到的最简单的优化方法,而且这样优化并不会影响应用的功能

这三个问题很容易就能回答,所以立即优化没有任何问题


1.怎么评估性能优化的效果?

解决性能问题的目的,自然是想得到一个性能提升的效果
为了评估这个效果,需要对系统的性能指标进行量化
并且要分别测试出优化前、后的性能指标,用前后指标的变化来对比呈现效果
1. 确定性能的量化指标
2. 测试优化前的性能指标
3. 测试优化后的性能指标


1. 确定性能的量化指标
性能的量化指标有很多,比如CPU使用率、应用程序的吞吐量、客户端请求的延迟等,都可以评估性能
不要局限在单一维度的指标上,至少要从应用程序和系统资源这两个维度,分别选择不同的指标
【举例】以Web应用为例
应用程序的维度,可以用吞吐量和请求延迟来评估应用程序的性能
系统资源的维度,我们可以用CPU使用率来评估系统的CPU使用情况

之所以从这两个不同维度选择指标,主要是因为应用程序和系统资源这两者间相辅相成的关系
好的应用程序是性能优化的最终目的和结果,系统优化总是为应用程序服务的
所以,必须要使用应用程序的指标,来评估性能优化的整体效果
系统资源的使用情况是影响应用程序性能的根源
所以,需要用系统资源的指标,来观察和分析瓶颈的来源



2. 测试优化前的性能指标
3. 测试优化后的性能指标
这两个步骤,主要是为了对比优化前后的性能,更直观地呈现效果
如果第一步,是从两个不同维度选择了多个指标,那么在性能测试时,就需要获得这些指标的具体数值
【举例】以刚刚的Web应用为例
对应上面提到的几个指标,可以选择ab等工具,测试Web应用的并发请求数和响应延迟
测试的同时,还可以用vmstat、pidstat等性能工具,观察系统和进程的CPU使用率
这样,我们就同时获得了应用程序和系统资源这两个维度的指标数值

不过,在进行性能测试时,有两个特别重要的地方需要注意下
第一,要避免性能测试工具干扰应用程序的性能,对Web应用来说,性能测试工具跟目标应用程序要在不同的机器上运行
第二,避免外部环境的变化影响性能指标的评估
这要求优化前、后的应用程序,都运行在相同配置的机器上并且它们的外部依赖也要完全一致



2.多个性能问题同时存在,要怎么选择?

系统性能总是牵一发而动全身,所以性能问题通常也不是独立存在的
那当多个性能问题同时发生的时候,应该先去优化哪一个呢?

在性能测试的领域,流传很广的一个说法是“二八原则”,也就是说80%的问题都是由20%的代码导致的
只要找出这20%的位置,就可以优化80%的性能
所以,并不是所有的性能问题都值得优化

动手优化之前先动脑,先把所有这些性能问题给分析一遍,找出最重要的、可以最大程度提升性能的问题,从它开始优化
这样的好处是,不仅性能提升的收益最大,而且很可能其他问题都不用优化,就已经满足了性能要求


怎么判断出哪个性能问题最重要?
这是性能分析要解决的核心问题,只不过这里要分析的对象,从原来的一个问题,变成了多个问题
思路其实还是一样的
所以,依然可以用前面讲过的方法挨个分析,分别找出它们的瓶颈
分析完所有问题后,再按照因果等关系,排除掉有因果关联的性能问题
最后,再对剩下的性能问题进行优化


如果剩下的问题还是好几个,就得分别进行性能测试了
比较不同的优化效果后,选择能明显提升性能的那个问题进行修复
这个过程通常会花费较多的时间,推荐两个可以简化这个过程的方法
第一,如果发现是系统资源达到了瓶颈,比如CPU使用率达到了100%,那么首先优化的一定是系统资源使用问题
完成系统资源瓶颈的优化后,才要考虑其他问题

第二,针对不同类型的指标,首先去优化那些由瓶颈导致的,性能指标变化幅度最大的问题
比如产生瓶颈后,用户CPU使用率升高了10%,而系统CPU使用率却升高了50%
这个时候就应该首先优化系统CPU的使用



3.有多种优化方法时,要如何选择?

当多种方法都可用时,应该选择哪一种呢?是不是最大提升性能的方法,一定最好呢?
一般情况下,当然想选能最大提升性能的方法,这其实也是性能优化的目标
但要注意,现实情况要考虑的因素却没那么简单
最直观来说,性能优化并非没有成本
性能优化通常会带来复杂度的提升,降低程序的可维护性,还可能在优化一个指标时,引发其他指标的异常
也就是说,很可能你优化了一个指标,另一个指标的性能却变差了


【举例】
一个很典型的例子是将在网络部分讲到的DPDK(Data Plane Development Kit)
DPDK是一种优化网络处理速度的方法,它通过绕开内核网络协议栈的方法,提升网络的处理能力
不过它有一个很典型的要求,就是要独占一个CPU以及一定数量的内存大页
并且总是以100%的CPU使用率运行,所以,如果你的CPU核数很少,就有点得不偿失了


所以,在考虑选哪个性能优化方法时,要综合多方面的因素
切记,不要想着“一步登天”,试图一次性解决所有问题
也不要只会“拿来主义”,把其他应用的优化方法原封不动拿来用,却不经过任何思考和分析




CPU优化

清楚了性能优化最基本的三个问题后
接下来从应用程序和系统的角度,分别来看看如何才能降低CPU使用率,提高CPU的并行处理能力



应用程序优化

从应用程序的角度来说,降低CPU使用率的最好方法当然是,排除所有不必要的工作,只保留最核心的逻辑
比如减少循环的层次、减少递归、减少动态内存分配等等


1.编译器优化

很多编译器都会提供优化选项,适当开启它们,在编译阶段你就可以获得编译器的帮助,来提升性能
比如,gcc就提供了优化选项-O2,开启后会自动对应用程序的代码进行优化


2.算法优化

使用复杂度更低的算法,可以显著加快处理速度
比如,在数据比较大的情况下,可以用 O(nlogn) 的排序算法(如快排、归并排序等)
代替 O(n^2) 的排序算法(如冒泡、插入排序等)


3.异步处理

使用异步处理,可以避免程序因为等待某个资源而一直阻塞,从而提升程序的并发处理能力
比如,把轮询替换为事件通知,就可以避免轮询耗费CPU的问题


4.多线程代替多进程

相对于进程的上下文切换,线程的上下文切换并不切换进程地址空间,因此可以降低上下文切换的成本


5.善用缓存

经常访问的数据或者计算过程中的步骤,可以放到内存中缓存起来,这样在下次用时就能直接从内存中获取
加快程序的处理速度



系统优化

从系统的角度来说,优化CPU的运行,一方面要充分利用CPU缓存的本地性,加速缓存访问
另一方面,就是要控制进程的CPU使用情况,减少进程间的相互影响


1.CPU绑定

把进程绑定到一个或者多个CPU上,可以提高CPU缓存的命中率,减少跨CPU调度带来的上下文切换问题


2.CPU独占

跟CPU绑定类似,进一步将CPU分组,并通过CPU亲和性机制为其分配进程
这样,这些CPU就由指定的进程独占,换句话说,不允许其他进程再来使用这些CPU


3.优先级调整

使用nice调整进程的优先级,正值调低优先级,负值调高优先级
在这里,适当降低非核心应用的优先级,增高核心应用的优先级,可以确保核心应用得到优先处理


4.为进程设置资源限制

使用Linux cgroups来设置进程的CPU使用上限,可以防止由于某个应用自身的问题,而耗尽系统资源


5.NUMA优化

支持NUMA的处理器会被划分为多个node,每个node都有自己的本地内存空间
NUMA优化,其实就是让CPU尽可能只访问本地内存


6.中断负载均衡

无论是软中断还是硬中断,它们的中断处理程序都可能会耗费大量的CPU
开启irqbalance服务或者配置smp_affinity,就可以把中断处理过程自动负载均衡到多个CPU上




千万避免过早优化

“过早优化是万恶之源”,过早优化不可取
一方面,优化会带来复杂性的提升,降低可维护性
另一方面,需求不是一成不变的。针对当前情况进行的优化,很可能并不适应快速变化的新需求
这样,在新需求出现时,这些复杂的优化,反而可能阻碍新功能的开发


所以,性能优化最好是逐步完善,动态进行,不追求一步到位,而要首先保证能满足当前的性能要求
当发现性能不满足要求或者出现性能瓶颈时,再根据性能评估的结果,选择最重要的性能问题进行优化


posted @ 2021-11-19 11:37  李成果  阅读(801)  评论(0编辑  收藏  举报