性能优化方法论
找出CPU的性能瓶颈之后,下一步要做的就是优化了,在优化之前,我们应该先看看下面这三个问题:
- 既然要做性能优化,那要怎么判断它是不是有效的呢?特别是优化后,到底能提升多少性能呢?
- 性能问题通常不是独立的,如果有多个性能问题同时发生,你应该先优化哪一个呢?
- 提升性能的方法并不是唯一的,当有多种方法可以选择时,你会选用哪一种呢?是不是总选那个最大程度提升性能的方法就行了呢?
如果你可以轻松回答这三个问题,那么⼆话不说就可以开始优化。
比如,在前面不可中断案例中,通过性能分析,我们发现是因为一个进程的直接IO,导致iowait高达90%,那是不是用“直接IO换成缓存IO”的方法,就立即可以优化了呢?
我们应该先思考上面三点之后才做出决定:
- 直接IO缓存缓存IO,可以把iowait降到接近0,性能提升很明显
- 我们没有发现其他性能问题,直接IO是唯一的性能瓶颈,所以不用挑选优化对象
- 缓存IO是我们目前用到的最简单的优化方法,而且这样优化不会影响应用的功能
好的,这三个问题很容易就能回答,所以⽴即优化没有任何问题。
但是,很多时候,性能评估可能有多重指标,性能问题可能会多个同时发⽣,⽽且,优化某⼀个指标的性能,可能⼜导致其他指标性能的下降。
那么,⾯对这种复杂的情况,我们该怎么办呢?
接下来,我们就来深⼊分析这三个问题。
怎么评估性能优化的结果
首先,来看第一个问题,怎么评估性能优化的结果。
我们解决性能问题的目的,自然是想得到一个性能提升的效果。为了评估这个效果,我们需要对系统的性能指标进行量化,并且要分布测试出优化前、后的性能指标,用前后指标的变化来对比呈现效果,即:
- 确定性能的量化指标。
- 测试优化前的性能指标。
- 测试优化后的性能指标。
先看第一步,性能的量化指标有很多,比如CPU使用率、应用程序的吞吐量、客户端请求的延迟等,都可以评估性能。那我们应该选择什么指标来评估呢?
建议:不要局限在单一维度的指标上,你至少应该从应用程序和系统资源这两个维度,分别选择不同的指标。比如,以web应用为例:
- 应用程序的维度,我们可以用吞吐量和请求延迟来评估应用程序的性能
- 系统资源的维度,我们可以用CPU使用率来评估系统的CPU使用情况。
之所以从这两个不同维度选择指标,主要是因为应⽤程序和系统资源这两者间相辅相成的关系。
- 好的应⽤程序是性能优化的最终⽬的和结果,系统优化总是为应⽤程序服务的。所以,必须要使⽤应⽤程序的指标,来评估性能优化的整体效果。
- 系统资源的使⽤情况是影响应⽤程序性能的根源。所以,需要⽤系统资源的指标,来观察和分析瓶颈的来源。
⾄于接下来的两个步骤,主要是为了对⽐优化前后的性能,更直观地呈现效果。如果你的第⼀步,是从两个不同维度选择了多个指标,那么在性能测试时,你就需要获得这些指标的具体数值。
那如何获得这些指标的具体数值呢?
还是以刚刚的 Web 应⽤为例,对应上⾯提到的⼏个指标,我们可以选择 ab 等⼯具,测试 Web 应⽤的并发请求数和响应延迟。⽽测试的同时,还可以⽤ vmstat、pidstat 等性能⼯具,观察系统和进程的 CPU 使⽤率。这样,我们就同时获得了应⽤程序和系统资源这两个维度的指标数值。
不过,在进⾏性能测试时,有两个特别重要的地⽅你需要注意下。
- 第⼀,要避免性能测试⼯具⼲扰应⽤程序的性能。通常,对 Web 应⽤来说,性能测试⼯具跟⽬标应⽤程序要在不同的机器上运⾏。
- 第⼆,避免外部环境的变化影响性能指标的评估。这要求优化前、后的应⽤程序,都运⾏在相同配置的机器上,并且它们的外部依赖(比如用相同参数的客户端工具)也要完全⼀致。
多个性能问题同时存在,要怎么选择?
系统性能总是牵⼀发⽽动全身,所以性能问题通常也不是独⽴存在的。那当多个性能问题同时发⽣的时候,应该先去优化哪⼀个呢?
- 在性能测试的领域,流传很⼴的⼀个说法是“⼆⼋原则”,也就是说 80% 的问题都是由 20% 的代码导致的。只要找出这 20%的位置,你就可以优化 80% 的性能。所以,并不是所有的性能问题都值得优化。
- 建议是,动⼿优化之前先动脑,先把所有这些性能问题给分析⼀遍,找出最重要的、可以最⼤程度提升性能的问题,从它开始优化。这样的好处是,不仅性能提升的收益最⼤,⽽且很可能其他问题都不⽤优化,就已经满⾜了性能要求。
那关键就在于,怎么判断出哪个性能问题最重要
挨个分析,分别找出它们的瓶颈。分析完所有问题后,再按照因果等关系,排除掉有因果关联的性能问题。最后,再对剩下的性能问题进⾏优化。
如果剩下的问题还是好⼏个,你就得分别进⾏性能测试了。⽐较不同的优化效果后,选择能明显提升性能的那个问题进⾏修复。这个过程通常会花费较多的时间,这⾥,推荐两个可以简化这个过程的⽅法。
- 第⼀,如果发现是系统资源达到了瓶颈,⽐如 CPU 使⽤率达到了 100%,那么⾸先优化的⼀定是系统资源使⽤问题。完成系统资源瓶颈的优化后,我们才要考虑其他问题。
- 第⼆,针对不同类型的指标,⾸先去优化那些由瓶颈导致的,性能指标变化幅度最⼤的问题。⽐如产⽣瓶颈后,⽤户 CPU 使⽤率升⾼了 10%,⽽系统 CPU 使⽤率却升⾼了 50%,这个时候就应该⾸先优化系统 CPU 的使⽤。
有多种优化⽅法时,要如何选择?
- ⼀般情况下,我们当然想选能最⼤提升性能的⽅法,这其实也是性能优化的⽬标。
- 但要注意,现实情况要考虑的因素却没那么简单。最直观来说,性能优化并⾮没有成本。性能优化通常会带来复杂度的提升,降低程序的可维护性,还可能在优化⼀个指标时,引发其他指标的异常。也就是说,很可能你优化了⼀个指标,另⼀个指标的性能却变差了。
一个典型的例子就是DPDK。DPDK是一种优化网络处理速度的方法,它通过绕开内核网络协议栈的方法,提升网络的处理能力。
不过它有一个很典型的要求,就是要独占一个CPU以及一定数量的内存大页,并且总是以100%使用率运行,可以,如果你的CPU核数很少,那么就得不偿失了
CPU优化
清楚了性能优化最基本的三个问题后,我们接下来从应⽤程序和系统的⻆度,分别来看看如何才能降低CPU 使⽤率,提⾼CPU 的并⾏处理能⼒。
应用程序优化
实现,从应用程序的角度来说,降低CPU使用率的最好方法是,排除所有不必要的工作,只保留最核心的逻辑。比如减少循环的次数、减少递归、减少动态内存分配等等
除此之外,一些常用方法如下:
- 编译器优化:很多编译器都会提供优化选项,适当开启它们,在编译阶段你就可以获得编译器的帮助,来提升性能。⽐如, gcc 就提供了优化选项 -O2,开启后会⾃动对应⽤程序的代码进⾏优化。
- 算法优化:使⽤复杂度更低的算法,可以显著加快处理速度。⽐如,在数据⽐较⼤的情况下,可以⽤ O(nlogn) 的排序算法(如快排、归并排序等),代替 O(n^2) 的排序算法(如冒泡、插⼊排序等)。
- 异步处理:使用异步处理,可以避免程序因为等待某个资源而一直阻塞,从而提升程序的并发处理能力。比如,把轮询替换为事件通知,就可以避免轮询耗费CPU的问题
- 多线程替换多进程:相对于进程的上下文切换,线程的上下文切换并不切换进程的地址空间,因此可以降低上下文切换的成功
- 善用缓存:经常访问的数据或者计算结果中的数据,可以放到内存中缓存起来,这样在下次用的时候就能直接从内存中获取,加快程序的处理步骤
系统优化
从系统的角度来说,优化CPU的运行:
- 一方面要充分利用CPU缓存的本地性,加速缓存访问;
- 另一方面,就是要控制进程的CPU使用情况,减少进程间的相互影响。
一些系统层面的CPU优化方法比较常用的方法:
- CPU绑定:
- 尝试使用
taskset
把进程绑定到一个或者多个CPU上,确保进程不会在处理器之间跳跃,导致cache刷新 - 这样可以提高CPU缓存的命中率,减少跨CPU调度带来的上下文切换问题
- 尝试使用
- CPU独占:
- 跟CPU绑定类似,进一步将CPU分组,并通过CPU亲和性机制为其分配进程。
- 这样,这些CPU就由指定的进程独占。换句话来说,不允许其他进程来使用这些CPU
- 优先级调整:
- 使用nice调整进程的优先级,正值调低优先级,负值调⾼优先级。
- 在这⾥,适当降低⾮核⼼应⽤的优先级,增⾼核⼼应⽤的优先级,可以确保核⼼应⽤得到优先处理(先使用
top
识别非关键性CPU密集型程序,并使用renice
修改它们的优先级)
- 为进程设置资源限制:
- 使⽤ Linux cgroups 来设置进程的 CPU 使⽤上限,可以防⽌由于某个应⽤⾃身的问题,⽽耗尽系统资源
- NUMA(Non-Uniform Memory Access)优化:
- 支持NUMA的处理器会被划分为多个 node,每个 node 都有⾃⼰的本地内存空间。
- NUMA 优化,其实就是让 CPU 尽可能只访问本地内存
- 中断负载均衡:
- ⽆论是软中断还是硬中断,它们的中断处理程序都可能会耗费⼤量的CPU。
- 开启 irqbalance 服务或者配置smp_affinity,就可以把中断处理过程⾃动负载均衡到多个 CPU 上。
其他手段:
- 使用
pf -ef
命令来确认后台没有运行不必要的程序。如果有,停止它们并使用cron
来安排它们在非高峰时段运行 - 根据正在运行的程序,可能更大的扩展(更大的CPU)要比更多的扩展(更多的CPU)好。比如,单线程应用程序可以用更快的CPU而不是更多的CPU
- 一般可以尝试使用最新的驱动程序和固件,因为这可能影响它们在CPU上的负载
千万避免过早优化
- ⼀⽅⾯,优化会带来复杂性的提升,降低可维护性;另⼀⽅⾯,需求不是⼀成不变的。针对当前情况进⾏的优化,很可能并不适应快速变化的新需求。这样,在新需求出现时,这些复杂的优化,反⽽可能阻碍新功能的开发。
- 所以,性能优化最好是逐步完善,动态进⾏,不追求⼀步到位,⽽要⾸先保证能满⾜当前的性能要求。当发现性能不满⾜要求或者出现性能瓶颈时,再根据性能评估的结果,选择最重要的性能问题进⾏优化。
⼀定要忍住“把 CPU 性能优化到极致”的冲动,因为 CPU 并不是唯⼀的性能因素。还有一些其他的问题⽐如内存、⽹络、I/O 甚⾄是架构设计的问题。
如果不做全⽅位的分析和测试,只是单纯地把某个指标提升到极致,并不⼀定能带来整体的收益。