
引言
如何才能做好性能优化
本书所有内容都是围绕着如何做好性能优化展开的,因此我们需要对性能优化有一个总体的认知,这样后面的学习才能更有方向性和目的性。
性能优化的本质
做任何事情,如果不了解其本质,就很难制定出真正有效的方案,所以了解性能优化的本质对于做好性能优化来说是一件很重要的事情。那么性能优化的本质是什么呢?笔者认为性能优化的本质是“通过充分且合理地使用设备的硬件资源,来让程序的用户体验更好,让企业获得收益”。
1.充分且合理地使用硬件资源
我们都知道性能优化的目的是让程序获得更好的用户体验,但是要怎样做才能获得更好的用户体验呢?我们可能会想出很多方案,比如使用预加载、多线程、缓存等。但这些方案真的能提升程序的用户体验吗?答案是“不确定”,有可能有正面效果,但是也有可能没效果,甚至可能带来负面效果。为什么会这样呢?我们要从硬件资源的角度去考虑这个问题。
如果程序当前的CPU使用率已经很高了,我们再预加载任务或使用多线程并发执行任务无疑是雪上加霜,不仅不会带来任何优化效果,还会导致劣化效果,只有CPU使用率较低的时候,这些方案才能带来较好的优化效果。如果当前的内存占用已经很多了,我们再使用更多的缓存,只会导致系统频繁触发GC(Garbage Collection,垃圾收集),甚至带来OOM(Out Of Memory,内存溢出)问题。很多时候,在进行性能优化时之所以出现效果不佳的情况,是因为我们在制定优化方案时,并没有从性能优化的本质去思考。只有充分且合理地使用硬件资源去制定优化方案,才能确保优化方案有效。
硬件资源包括CPU、内存、磁盘、电池等。因为不同设备的硬件资源不尽相同,所以当我们基于本质来进行性能优化时,提出的方案和前面的方案就不一样了。下面以CPU和内存为例进行说明。
❑CPU:基于如何合理且充分地使用CPU来进行性能优化时,我们需要针对不同性能的CPU采取不同的优化方案。对于中低端机型来说,由于CPU性能差,所以很容易过载,此时需要考虑通过降低CPU的消耗来合理地使用CPU资源,常见的方案有减少线程、减少和关闭预加载任务等,这样就可以将更多的CPU资源分配给主线程或者核心任务使用。而对于高端机型来说,因为CPU性能好,我们的优化方案往往围绕“如何充分使用CPU资源”来制定,所以此时的优化方案和中低端机型刚好相反,可以使用更多的线程来并发执行任务、使用更多的预加载任务来提升用户体验。
❑内存:基于如何合理且充分地使用内存资源来进行性能优化时,我们需要针对内存资源的大小来设置缓存大小。对于大内存的设备,可以将缓存设置得大一些,让它可以存储更多数据;对于小内存的设备,则需要把缓存设置得小一些。缓存的大小要始终控制在合理的范围内,这样才能保证既充分使用内存资源来提高程序的性能表现,又不会因为内存使用过多而出现OOM等稳定性问题。
通过上面的两个例子我们会发现:基于本质制定的优化方案都是有效的。
2.取得收益
制定方案并进行优化,只是性能优化工作中的一部分,我们还需要做一件同样重要的事情——取得收益,其中又包括两件事情,一是制定指标,二是采集指标。
(1)制定指标
既然要取得收益,第一步一定是制定指标。我们通常会选取通用的性能指标来度量优化的效果和收益。比如:度量速度的指标有启动速度、页面打开速度等;度量内存的指标有PSS(程序在系统中占用的实际物理内存量)、Java内存占用率、Native内存占用率等;度量稳定性的指标包括Crash(崩溃)率、OOM率等。但是在选取这些指标时,我们还需要进一步思考这些指标是否真的能够体现收益。
比如进行内存优化,我们很可能会选取PSS这个指标来度量内存优化的收益,经过一系列的优化,我们成功地让PSS减少了100MB,此时我们很可能会认为自己的优化带来了不错的收益。但是实际上PSS减少100MB后,程序的表现到底是更好还是更坏?这其实是不能确定的。可能因为我们减少了缓存的数据,所以PSS少了100MB,这个时候程序中某些页面的打开速度变慢了,所以性能体验变差了;也可能因为减少的这100MB PSS让程序的OOM率大幅降低了,用户体验提升了。
但是如果我们将内存优化的度量指标由PSS值改为OOM率、GC次数等指标,就能更容易地判断程序对用户体验的影响。所以我们在制定性能优化的度量指标时,要选取那些能真正体现程序对用户体验和收益的影响的指标。
(2)采集指标
制定指标之后就要采集指标了。采集指标时需要做到数据准确、一致并尽量减少对性能的损耗。
数据的准确性我们都能理解,就是采集的性能指标数据是准确的。那么,什么是数据的一致性呢?很多指标是比较主观的指标,比如页面打开速度,这个指标就涉及页面打开的结束点的选取,什么情况下算页面打开完成?这个问题没有标准答案,我们可以认为某些关键组件渲染完成算打开完成,可以认为大部分UI都展示出来了才算打开完成,也可以简单地认为第1帧渲染完成了就算打开完成。我们需要结合自身程序的特点,选择一个大家都能认可的点作为结束点,并且后续要始终以这个点作为结束点。在性能优化中,指标是很重要的,所以它的基准需要始终保持一致,如果标准总是变化,那么基准也就变得没有意义了。没有了指标,我们就无法明确不同的版本对用户体验来说是优化还是劣化。
在指标采集过程中,我们也要关注采集行为对性能的影响。不少指标都需要进行I/O(输入/输出)操作,比如采集内存相关的数据、采集CPU相关的数据,I/O本身就是消耗资源的操作,所以需要控制好采集频率,尽量减少采集对性能的损耗。
性能优化的维度
了解性能优化的本质是设计出有效的优化方案的基础,但并不代表我们就能设计出高体系化的优化方案,毕竟性能优化是一项庞大的工程。我们此时可能只有一些零碎的想法,根本无法形成体系和全面的优化方案,但只有体系且全面的优化方案才能将优化做到最好。如图0-1所示,想要打造体系化的优化方案,我们还需要基于应用层、系统层、硬件层3个维度来进行。

图0-1 性能优化的维度
1.应用层
应用层主要指的是我们开发的程序,针对应用层的优化是开发者做得最多的优化工作。在做优化时,我们通常会了解业务逻辑,然后通过多线程、预加载、缓存等手段进行优化。但仅做到这样,优化效果是很有限的。我们还需要基于性能优化的本质来进行思考,也就是如何充分且合理地使用硬件资源。因此,针对应用层的优化通常有两个方向:一是如何让业务更加充分地使用CPU、缓存等硬件资源;二是如何管控业务方,让其可以合理使用硬件资源。
所以想要有更好的效果,我们不仅需要了解各个业务逻辑,还要清楚业务对资源的消耗,例如每个业务消耗了多少内存资源、消耗了多少CPU资源、使用了多少个线程等。我们只有把这些都摸透之后,才能开始进行优化。大型应用要面对的业务多,资源消耗往往都是过载的,优化方案的重点是如何分配和管理业务对资源的使用,所以我们可以通过启动框架、预加载框架、降级框架等来约束和管控业务方对资源的使用;中小型应用面对的业务较少,资源消耗往往都是不足的,所以我们可以通过更多的预加载任务、多线程等方案来提升资源的使用率。
2.系统层
系统层指的是Android系统和Linux系统,针对系统层的优化要比针对应用层的优化难很多。因为想要针对系统层进行优化,就要熟悉系统知识,而熟悉Android系统和Linux系统的原理与特性,比熟悉程序的业务逻辑要复杂太多了。另外,由于我们无法直接控制系统层的逻辑,所以经常需要使用一些复杂的技术(如Native Hook技术)来达到优化的目的。
系统层的优化通常都是以减少系统自身的资源消耗为主,比如系统在进行GC时,频繁切换线程、频繁缺页中断和换页,都会消耗大量的CPU资源,所以我们要想办法减少这些系统逻辑的资源消耗,或者降低这些系统逻辑在执行时对应用的影响。
虽然针对系统层的优化很复杂,但很多已有的优化方案可以直接使用或借鉴,因为针对系统层的优化一般都是通用的,比如在启动时进行GC抑制、合理设计线程池等。
3.硬件层
针对硬件层的优化需要先了解硬件的特性,然后寻找优化点。大部分硬件层的优化方案都是针对CPU和缓存这两个硬件的特性来展开的。比如,CPU由大小核组成,大核的运行频率高,小核的运行频率低,我们可以将主线程放在大核上运行;如果厂商提供了相应的超频API,我们也可以在核心场景中将CPU提频以提高性能。
缓存的架构由多级高速缓存、内存、磁盘组成,针对缓存这一硬件进行优化时,我们可以思考如何提升高速缓存的命中率和内存的命中率。
性能优化的难点
到这里,我们已经知道性能优化的本质和维度了,但是此时我们只知道前方的路在哪里,想要到达终点还需要不断前行,并克服路上遇到的重重障碍。这条路上的障碍就是性能优化的难点,主要涉及4个方面:知识储备、思考的角度、思考的方式、优化的流程。
1.知识储备
想要做好性能优化,首先需要具有完备的知识体系,前文提到性能优化要从应用层、系统层和硬件层3个维度进行,这就意味着我们要扎实地掌握这3个维度的知识点。
❑应用层:我们想在针对应用层的性能优化中取得好的效果,就需要加深对所开发的应用的了解。我们需要知道自己所负责的App有哪些线程,都是干什么的,都有哪些业务在使用,这些线程要消耗多少CPU资源;内存占用多少,都是哪些业务占用的,缓存命中率多少;启动过程中、核心页面打开过程中都做了哪些事情,I/O阻塞耗时是多少,逻辑耗时是多少,CPU使用率是多少……如果我们能对应用有全面和透彻的了解,那在进行性能优化时,自然就能像庖丁解牛一般顺畅。
❑系统层:系统层的知识点相比应用层的知识点,数量更加庞大,也更加复杂。如Linux系统的相关知识包括进程管理和调度、内存管理、虚拟内存、锁、IPC等;Android系统的相关知识包括虚拟机、核心服务、渲染、核心流程等。针对系统层的性能优化,一定是建立在对系统的机制和流程充分掌握的基础之上。如果我们不了解Linux系统的进程调度机制,就无法充分利用进程优先来提升性能;如果我们不熟悉Android系统的虚拟机,那么围绕虚拟机的一些优化,比如OOM优化、GC优化等,都无法很好地开展和落地。
❑硬件层:对于硬件层来说,我们需要熟悉CPU、缓存等硬件的特性。如果能知道CPU由几个核组成,哪些是大核,哪些是小核,我们自然就会想到可以将核心线程绑定在大核上运行,以此来进行性能优化;如果能了解存储结构中寄存器、高速缓存、主存的设计,我们自然就会想到将核心数据尽量放在高速缓存中。
除了上面提到的知识,如果想要在性能优化上更进一步,我们还需要掌握汇编、编译器、编程语言、逆向等知识。比如我们知道了用C++写的代码比用Java写的代码运行得快,就可以将一些业务需求用C++代码来实现从而提高性能。类似的方案还有:通过优化编译器的内联、消除无用代码等来减小包体积,通过逆向技术优化系统的逻辑。
可以看到,想要精通性能优化,需要庞大的知识储备,所以性能优化非常能体现开发者的技术深度和广度。不管是在面试中还是在工作中,擅长性能优化一定会成为我们的加分项。
2.思考的角度
在处理一件复杂且庞大的事情时,我们首先要做的是分层和分类,不同的层和类代表着不同的角度。比如在构建客户端程序架构时,通常都是采用分层架构,不同层的逻辑和职责是不一样的;在成体系地进行性能优化时,我们可以从应用层、系统层、硬件层分别进行优化,每一层的优化方案也不尽相同。
除了针对事情本身从不同角度进行思考外,我们还可以跳出事情本身,以获取更多的灵感。比如跳出本设备来思考:是否可以用其他设备帮助我们加速启动?Google Play(谷歌的应用商店)就有类似的优化方案。Google Play会上传一些其他机器已经编译好的机器码,相同设备下载这个应用时会带着这些编译好的机器码。还有很常用的服务器端渲染技术,也就是让服务器端先渲染好界面,然后直接使用静态模块来提升页面打开速度。又或者站在用户的角度去思考,想一想到底什么样的优化给用户的感知是正向的,比如有时候我们在做启动和页面打开速度优化时,可以先给用户一个假的静态页面,让用户感知到页面已经打开了,然后再去绑定真实的数据。
做性能优化时,我们考虑的角度越多、越全面,优化方案能取得的效果就越好。
3.思考的方式
在处理复杂的事情时,我们可以从更多的角度去思考,从而得到更多的优化方案。但是我们要怎样去得到这些思考角度呢?如果我们就是想不出这些角度怎么办?其实这些都和我们思考问题的方式有关,不同的思考方式会帮助我们获得不一样的思考角度,最常见的思考方式有两种——自上而下和自下而上。
❑自上而下:自上而下的思考方式是一种从整体到局部进行逐步分解并各个击破的思维方式。在大型应用的性能优化中,自上而下是一种很常用的思维方式。大型应用面对的业务非常多,不同业务的负责团队都不一样,所以我们在做性能优化时,需要从整体来考虑如何管控和分配业务对资源的消耗及使用。我们可以设计一些全局的框架来管控业务对资源的使用,如预加载框架、降级框架,或设计一些全局的监控来度量业务对资源的使用。当我们有了整体的管控和监控后,就可以进入局部视角,对资源消耗大的业务进行优化。
❑自下而上:自下而上的思考方式刚好相反,它是一种从细节或者底层原理逐步向上构建整体解决方案的思维方式。比如当我们做速度的优化时,直接从影响速度的CPU、缓存等思考如何提高CPU的利用率和缓存的命中率,并从硬件层、系统层、应用层自下而上地逐一进行思考,最终构建出完整的优化方案。
不同的思考方式最终会让我们设计出不同的优化方案,但并不是说哪种思考方式就更胜一筹。在面对复杂问题时,我们需要尝试使用不同的思考方式来思考并解决问题。
4.优化的流程
性能优化的完整流程如图0-2所示,监控、优化、数据收益获取、防劣化这些工作组合到一起形成一个闭环,在做性能优化时,我们需要把各个环节都考虑到。
❑监控:监控应用运行过程中的各项性能指标。想要做好监控,除了需要尽量减少监控逻辑对性能的损耗外,还需要尽量做到对归因的监控。比如对内存的监控,除了监控应用的内存指标外,还要监控大集合、大图片、大对象等归因项的内存使用占比,这样的监控可以帮助我们直接定位问题。完整和优秀的监控方案能让我们更高效地发现和解决异常。
❑优化:关于优化,这里只强调一点,即性能优化只是优化中的一个环节,并不是优化的全部。
❑数据收益获取:该环节不是简单地观察指标的变化,还要做A/B测试、关注核心价值指标。比如做内存优化,我们不能一味地追求减少应用的PSS,这个指标并不能直接代表真实的用户体验,所以在做内存优化时,我们最好能结合内存触顶率、崩溃率、用户留存率等与用户体验直接相关的核心价值指标,来获取内存优化的收益。
❑防劣化:防劣化也有很多事情可以做,如建立完善的线下性能测试、线上监控报警等机制。同样以内存优化为例,我们可以在线下每天通过Monkey来测试内存泄漏情况,发现问题并及时治理,这就是防劣化工作。

图0-2 性能优化的完整流程
读到这里,我们应对性能优化具有了基本认知,下面就让我们正式开启性能优化之旅吧!