相当于是前面篇章的小结
一、 CPU 性能指标
常见指标包括:
- 平均负载
- CPU 使用率(user、iowait、system、软硬中断等)
- 进程上下文切换(自愿、非自愿)
- CPU 缓存的命中率
CPU 的处理速度就比内存的访问速度快得多。这样,CPU 在访问内存的时候,免不了要等待内存的响应。为了协调这两者巨大的性能差距,CPU 缓存(通常是多级缓存)就出现了。
CPU 缓存的速度介于 CPU 和内存之间,缓存的是热点的内存数据。根据不断增长的热点数据,这些缓存按照大小不同分为 L1、L2、L3 等三级缓存,其中L1 和 L2 常用在单核中, L3 则用在多核中。
从 L1 到 L3,三级缓存的大小依次增大,相应的,性能依次降低(当然比内存还是好得多)。而它们的命中率,衡量的是 CPU 缓存的复用情况,命中率越高,则表示性能越好。
二、 性能工具
活学活用,把性能指标和性能工具联系起来。
第一个维度,从性能指标出发。也就是说,当你要查看某个性能指标时,要清楚知道哪些工具可以做到。根据不同的性能指标,对提供指标的性能工具进行分类和理解。这样,在实际排查性能问题时,你就可以清楚知道,什么工具可以提供你想要的指标,而不是毫无根据地挨个尝试,撞运气。
总结一下前面案例用到的工具:
- 平均负载:uptime/top + mpstat + pidstat
- 上下文切换:vmstat + pidstat
- CPU 使用率:top + perf top 或 perf record 和 perf report,对于大量短时进程,可以用execsnoop
- IO问题:top/ps + dstat + pidstat + strace + perf record 和 perf report
- 僵尸进程:top/ps + pstree
第二个维度,从工具出发。也就是当你已经安装了某个工具后,要知道这个工具能提供哪些指标。
这在实际环境特别是生产环境中也是非常重要的,因为很多情况下,你并没有权限安装新的工具包,只能最大化地利用好系统中已经安装好的工具,这就需要你对它们有足够的了解。具体到每个工具的使用方法,一般都支持丰富的配置选项。不过不用担心,这些配置选项并不用背下来。你只要知道有哪些工具、以及这些工具的基本功能是什么就够了。真正要用到的时候, 通过 man 命令,查它们的使用手册就可以了。
三、 如何迅速分析 CPU 的性能瓶颈
虽然 CPU 的性能指标比较多,但要知道,既然都是描述系统的 CPU 性能,它们就不会是完全孤立的,很多指标间都有一定的关联。想弄清楚性能指标的关联性,就要通晓每种性能指标的工作原理。这也是为什么我在介绍每个性能指标时,都要穿插讲解相关的系统原理,希望你能记住这一点。
举个例子,用户 CPU 使用率高,我们应该去排查进程的用户态而不是内核态。因为用户CPU 使用率反映的就是用户态的 CPU 使用情况,而内核态的 CPU 使用情况只会反映到系统 CPU 使用率上。
你看,有这样的基本认识,我们就可以缩小排查的范围,省时省力。所以,为了缩小排查范围,我通常会先运行几个支持指标较多的工具,如 top、vmstat 和pidstat 。为什么是这三个工具呢?仔细看看下面这张图,你就清楚了。
这三个工具输出的很多指标是相互关联的,所以,我也用虚线表示了它们的关联关系,举几个例子你可能会更容易理解。
第一个例子,pidstat 输出的进程用户 CPU 使用率升高,会导致 top 输出的用户 CPU 使用率升高。所以,当发现 top 输出的用户 CPU 使用率有问题时,可以跟 pidstat 的输出做对比,观察是否是某个进程导致的问题。而找出导致性能问题的进程后,就要用进程分析工具来分析进程的行为,比如使用 strace分析系统调用情况,以及使用 perf 分析调用链中各级函数的执行情况。
第二个例子,top 输出的平均负载升高,可以跟 vmstat 输出的运行状态和不可中断状态的进程数做对比,观察是哪种进程导致的负载升高。
- 如果是不可中断进程数增多了,那么就需要做 I/O 的分析,也就是用 dstat 或 sar 等工具,进一步分析 I/O 的情况。
- 如果是运行状态进程数增多了,那就需要回到 top 和 pidstat,找出这些处于运行状态的到底是什么进程,然后再用进程分析工具,做进一步分析
最后一个例子,当发现 top 输出的软中断 CPU 使用率升高时,可以查看 /proc/softirqs文件中各种类型软中断的变化情况,确定到底是哪种软中断出的问题。比如,发现是网络接收中断导致的问题,就可以继续用网络分析工具 sar 和 tcpdump 来分析。
四、 性能优化方法论
在我们历经千辛万苦,通过各种性能分析方法,终于找到引发性能问题的瓶颈后,是不是立刻就要开始优化了呢?别急,动手之前,你可以先看看下面这三个问题。
- 怎么判断优化是不是有效呢?特别是优化后,到底能提升多少性能
- 性能问题通常不是独立的,如果有多个性能问题同时发生,你应该先优化哪一个
- 提升性能的方法并不是唯一的,当有多种方法可以选择时,你会选用哪一种?是不是总选提升性能最多的方法就行了呢
如果你可以轻松回答这三个问题,那么二话不说就可以开始优化。
1. 怎么评估性能优化的效果
为了评估这个效果,我们需要对系统的性能指标进行量化,并且要分别测试出优化前、后的性能指标,用前后指标的变
化来对比呈现效果。我把这个方法叫做性能评估“三步走”。
- 确定性能的量化指标
- 测试优化前的性能指标
- 测试优化后的性能指标
先看第一步,性能的量化指标有很多,比如 CPU 使用率、应用程序的吞吐量、客户端请求的延迟等,都可以评估性能。那我们应该选择什么指标来评估呢?我的建议是不要局限在单一维度的指标上,你至少要从应用程序和系统资源这两个维度,分别选择不同的指标。比如,以 Web 应用为例:
- 应用程序的维度,可以用吞吐量和请求延迟来评估应用程序的性能。好的应用程序是性能优化的最终目的和结果,系统优化总是为应用程序服务的。所以,必须要使用应用程序的指标,来评估性能优化的整体效果。
- 系统资源的维度,可以用 CPU 使用率来评估系统的 CPU 使用情况。系统资源的使用情况是影响应用程序性能的根源。所以,需要用系统资源的指标,来观察和分析瓶颈的来源。
至于接下来的两个步骤,主要是为了对比优化前后的性能,更直观地呈现效果。如果你的第一步,是从两个不同维度选择了多个指标,那么在性能测试时,你就需要获得这些指标的具体数值。
不过,在进行性能测试时,有两个特别重要的地方你需要注意下。
- 第一,要避免性能测试工具干扰应用程序的性能。通常,对 Web 应用来说,性能测试工具跟目标应用程序要在不同的机器上运行。比如,在之前的 Nginx 案例中,我每次都会强调要用两台虚拟机,其中一台运行 Nginx 服务,而另一台运行模拟客户端的工具,就是为了避免这个影响。
- 第二,避免外部环境的变化影响性能指标的评估。这要求优化前、后的应用程序,都运行在相同配置的机器上,并且它们的外部依赖也要完全一致。
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 的并行处理能力。
1. 应用程序优化
首先,从应用程序的角度来说,降低 CPU 使用率的最好方法当然是,排除所有不必要的工作,只保留最核心的逻辑。比如减少循环的层次、减少递归、减少动态内存分配等等。除此之外,应用程序的性能优化也包括很多种方法,我在这里列出了最常见的几种,你可以记下来。
- 编译器优化:很多编译器都会提供优化选项,适当开启它们,在编译阶段你就可以获得编译器的帮助,来提升性能。比如, gcc 就提供了优化选项 -O2,开启后会自动对应用程序的代码进行优化。
- 算法优化:使用复杂度更低的算法,可以显著加快处理速度。比如,在数据比较大的情况下,可以用 O(nlogn) 的排序算法(如快排、归并排序等),代替 O(n^2) 的排序算法(如冒泡、插入排序等)。
- 异步处理:使用异步处理,可以避免程序因为等待某个资源而一直阻塞,从而提升程序的并发处理能力。比如,把轮询替换为事件通知,就可以避免轮询耗费 CPU 的问题。
- 多线程代替多进程:相对于进程的上下文切换,线程的上下文切换不切换进程地址空间,可以降低上下文切换的成本。
- 善用缓存:经常访问的数据或者计算过程中的步骤,可以放到内存中缓存起来,这样在下次用时就能直接从内存中获取,加快程序的处理速度。
2. 系统优化
从系统的角度来说,优化 CPU 的运行,一方面要充分利用 CPU 缓存的本地性,加速缓存访问;另一方面,就是要控制进程的 CPU 使用情况,减少进程间的相互影响。
- CPU 绑定:把进程绑定到一个或者多个 CPU 上,可以提高 CPU 缓存的命中率,减少跨CPU 调度带来的上下文切换问题。
- CPU 独占:跟 CPU 绑定类似,进一步将 CPU 分组,并通过 CPU 亲和性机制为其分配进程,不允许其他进程再来使用这些CPU。
- 优先级调整:使用 nice 调整进程的优先级(正低负高)。适当降低非核心应用的优先级,增高核心应用的优先级,可以确保核心应用得到优先处理。
- 为进程设置资源限制:使用 Linux cgroups 设置进程的 CPU 使用上限,防止由于某个应用自身的问题耗尽系统资源。
- NUMA(Non-Uniform Memory Access)优化:支持 NUMA 的处理器会被划分为多个 node,每个 node 都有自己的本地内存空间。NUMA 优化,其实就是让 CPU 尽可能只访问本地内存。
- 中断负载均衡:无论是软中断还是硬中断,它们的中断处理程序都可能会耗费大量的CPU。开启 irqbalance 服务或者配置 smp_affinity,就可以把中断处理过程自动负载均衡到多个 CPU 上
六、 千万避免过早优化
掌握上面这些优化方法后,我估计,很多人即使没发现性能瓶颈,也会忍不住把各种各样的优化方法带到实际的开发中。
不过,我想你一定听说过高德纳的这句名言, “过早优化是万恶之源”,我也非常赞同这一点,过早优化不可取。
因为,一方面,优化会带来复杂性的提升,降低可维护性;另一方面,需求不是一成不变的。针对当前情况进行的优化,很可能并不适应快速变化的新需求。这样,在新需求出现时,这些复杂的优化,反而可能阻碍新功能的开发。
所以,性能优化最好是逐步完善,动态进行,不追求一步到位,而要首先保证能满足当前的性能要求。当发现性能不满足要求或者出现性能瓶颈时,再根据性能评估的结果,选择最重要的性能问题进行优化。