运用的速度优化是咱们运用最频繁,也是运用最重要的优化之一,它包含发动速度优化,页面翻开速度优化,功能或业务履行速度优化等等,能够直接进步运用的用户体会。因而,只要是 Android 开发者,必定或多或少有过速度相关的优化经历。可是大部分人谈到速度优化,只能想到一些零碎的优化点,比方运用多线程、预加载等等。这对速度的进步必定是不行的,想要做得更好,咱们无妨来思考下面几个问题:
-
咱们的优化计划是全面且系统的吗?
-
咱们的计划为什么能进步速度呢?
-
咱们的计划作用怎样?
想要回答好这几个问题,咱们就需求了解影响和决议运用速度的底层原理及实质。那从底层来看,CPU、缓存、使命调度才是决议运用速度最实质的要素。CPU 和缓存都归于硬件层,使命调度机制则归于操作系统层。
那这一节课,咱们就一同深化硬件和操作系统层面去了解以上三个要素是怎么决议运用速度的,重新认识运用的速度优化,由下而上地建立起速度优化的认知系统和办法。
怎么从 CPU 层面进行速度优化?
咱们知道,一切的程序终究会被编译成机器码指令,然后交给 CPU 履行,CPU 以流水线的办法一条一条履行程序的机器码指令。当咱们想要进步某些场景(如发动、翻开页面、滑动等)的速度时,实质上便是下降 CPU 履行完这些场景指令的时刻,这个时刻简称为 CPU 时刻。想要下降 CPU 时刻,咱们需求先知道程序所耗费 CPU 时刻的计算公式:CPU 时刻=程序的指令数 x 时钟周期时刻 x 每条指令的均匀时钟周期数。下面一一解释一下这三项因子的含义。
-
程序的指令数:这一项很好了解,便是程序编译成机器码指令后的指令数量。
-
时钟周期时刻:每一次时钟周期内,CPU 仅完结一次履行,所以时钟周期时刻越短,CPU 履行得越快。或许你对时钟周期时刻不了解,可是它的倒数也便是时钟周期频率,你必定听说过。1 纳秒的时钟周期时刻便是 1 GHZ 的时钟周期频率,厂商发布新手机或许咱们购买新手机时,都或多或少会提到 CPU 的时钟频率,比方高通骁龙 888 这款 CPU 的时钟频率是 2.8 GHZ,这个指标也是衡量 CPU 功能最重要的一个指标。
-
每条指令的均匀时刻周期:是指令履行完毕所耗费的均匀时刻周期,指令不同所需的机器周期数也不同。关于一些简单的单字节指令,在取指令周期中,指令取出到指令寄存器后会立即译码履行,不再需求其它的机器周期。关于一些比较复杂的指令,例如转移指令、乘法指令,则需求两个或许两个以上的机器周期。
从 CPU 来看,当咱们想要进步程序的速度时,优化这三项因子中的任何一项都能够达到目的。那基于这三项因子有哪些通用计划能够借鉴呢?
削减程序的指令数
经过削减程序的指令数来进步速度,是咱们最常用也是优化计划最多的办法,比方下面这些计划都是经过削减指令数来进步速度的。
-
运用手机的多核:当咱们将要提速的场景的程序指令交给多个 CPU 一起履行时,关于单个 CPU 来说,需求履行的指令数就变少了,那 CPU 时刻自然就下降了,也便是并发的思维。但要留意的是,并发只要在多核下才干完成,假如只要一个 CPU,即便咱们将场景的指令拆分红多份,关于这个 CPU 来说,程序的指令数仍然没有变少。怎么才干发挥机器的多核呢?运用多线程即可,假如咱们的手机是 4 核的,就能一起并发的运转 4 个线程。
-
更简练的代码逻辑和更优的算法:这一点很好了解,相同的功能用更简练或更优的代码来完成,指令数也会削减,指令数少了程序的速度自然也就快了。具体落地这一类优化时,咱们能够用抓 trace 或许在函数前后统计耗时的办法去分析耗时,将这些耗时久的办法用更优的办法完成。
-
削减 CPU 的搁置:经过在 CPU 搁置的时分,履行预创立 View,预准备数据等预加载逻辑,也是削减指令数的一种优化计划,咱们需求加快场景的指令数量由于预加载履行了一部分而变少了,自然也就快了。
-
经过其他设备来削减当前设备程序的指令数:这一点也衍生许多优化计划,比方 Google 商店会把某些设备中程序的机器码上传,这样其他用户下载这个程序时,便不需求自己的设备再进行编译操作,由于进步了安装或许发动速度。再比方在翻开一些 WebView 网页时,服务端会经过预渲染处理,将 IO 数据都处理完结,直接展现给用户一个静态页面,这样就能极大进步页面翻开速度。
上面提到的这些计划都是咱们最常用的计划,基于指令数这一基本原理,还能衍生出许多计划来进步速度,这儿无法一一列全,咱们也能够自己想一想还能扩展出哪些计划出来。
下降时钟周期时刻
想要下降手机的时钟周期,一般只能经过晋级 CPU 做到,每次新出一款 CPU,相比上一代,不仅在时钟周期时刻上有优化,每个周期内可履行的指令也都会有优化。比方高通骁龙 888 这款 CPU 的大核时钟周期频率为 2.84GHz,而最新的 Gen 2 这款 CPU 则达到了 3.50GHz。
尽管咱们无法下降设备的时钟周期,可是应该避免设备进步时钟周期时刻,也便是降频现象,当手机发热发烫时,CPU 往往都会经过降频来削减设备的发热现象,具体的办法便是经过合理的线程运用或许代码逻辑优化,来削减程序长时刻超负荷的运用 CPU。
下降每条指令的均匀时刻周期
在下降每条指令的均匀时刻周期上,咱们能做的其实也不多,由于它和 CPU 的功能有很大的联系,但除了 CPU 的功能,以下几个方面也会影响到指令的时刻周期。
-
编程言语:Java 翻译成机器码后有更多的简介调用,所以比 C++ 代码编译成的机器码指令的均匀时刻周期更长。
-
编译程序:一个好的编译程序能够经过优化指令来下降程序指令的均匀时刻周期。
-
下降 IO 等候:从严厉含义来说,IO 等候的时刻并不能算到指令履行的耗时中,由于 CPU 在等候 IO 时会休眠或许去履行其他使命。可是等候 IO 会使履行完指令的时刻变长,所以这儿仍然把削减 IO 等候算入是下降每条指令的均匀时刻周期的优化计划之一。
怎么从缓存层面进行速度优化?
程序的指令并不是直接就能被 CPU 履行的,而是要放在缓存中,CPU 从缓存中读取,而且一个程序也不或许全是 CPU 计算逻辑,必定也会涉及到 IO 的操作或等候,比方往磁盘或许内存中读写数据成功后才干继续履行后边的逻辑,所以缓存也是决议运用速度的要害要素之一。缓存对程序速度的影响首要表现在 2 个方面:
-
缓存的读写速度;
-
缓存的命中率。
下面就具体讲解一下这 2 方面对速度的影响。
缓存的读写速度
手机或电脑的存储设备都被安排成了一个存储器层次结构,在这个层次结构中,从上至下,设备的拜访速度越来越慢,但容量也越来越大,而且每字节的造价也越来越廉价。寄存器文件在层次结构中坐落最顶部,也便是第 0 级。下图展现的是三层高速缓存的存储结构。
高速缓存是归于 CPU 的组成部分,而且实践有几层高速缓存也是由 CPU 决议的。以下图高通骁龙 888 的芯片为例,它是 8 块核组成的 CPU,从架构图上能够看到,它的 L2 是 1M 巨细(没有 L1 是由于这其实只是序号称呼上的不同罢了,你也能够了解成 L1),L3 是 3M 巨细,而且一切核共享。
不同层之间的读写速度距离是很大的,所以为了能进步场景的速度,咱们需求将和中心场景相关的资源(代码、数据等)尽量存储在靠上层的存储器中。 基于这一原理,便能衍生出了十分多的优化计划,比方常用的加载图片的框架 Fresco,恳求网络的框架 OkHttp 等等,都会想尽办法将数据缓存在内存中,其次是磁盘中,以此来进步速度。
缓存的命中率
将数据放在缓存中是一种十分入门的优化思维,也是十分简单办到的,即便是开发新手都能想到以此来进步速度。可是咱们的缓存容量是有限的,越上层的缓存尽管拜访越快,可是容量越少,价格也越贵,所以咱们只能将有限的数据存放在缓存中,在这样的限制下,进步缓存的命中率往往是一件十分难的工作。
一个好的编译器能够进步寄存器的命中率,好的操作系统能够进步高速缓存的命中率,关于咱们运用来说,好的优化计划能够进步主存和硬盘的命中率,比方咱们常用的 LruCache 等数据结构都是用来进步主存命中率的。除了进步运用的主存,运用也能够进步高速缓存的命中率,只是能做的工作不多,后边的章节中也会介绍怎么经过 Dex 中 class 文件重排,来进步高速缓存读取类文件时的命中率。
想要进步缓存命中率,一般都是运用局部性原理(局部性原理指假如某数据被拜访,则不久之后该数据或许再次被拜访,或许程序拜访了某个存储单元,则不久之后,其附近的存储单元也将被拜访)或许经过行为猜测,分析大概率事情等多种原理来进步缓存命中率。
怎么从使命调度层面进行速度优化?
咱们学过操作系统为了能一起运转多个程序,所以诞生了虚拟内存这个技能,但只要虚拟内存技能是不行的,还需求使命调度机制,所以使命调度也归于操作系统要害的组成之一。有了使命调度机制,咱们的程序才干取得 CPU 的资源并正常跑起来,所以使命调度也是影响程序速度的实质要素之一。
咱们从两个方面来了解使命调度机制,一是调度机制的原理,二是使命的载体,即进程的生命周期。
在 Linux 系统中,使命调度的维度是进程,Java 线程也归于轻量级的进程,所以线程也是遵从 Linux 系统的使命调度规矩的,那进程的调度规矩又是怎样的呢?Linux 系统将进程分为了实时进程和一般进程这两类,实时进程需求呼应技能的进程,比方 UI 交互进程,而一般进程对呼应速度要求不是十分高,比方读写文件、下载等进程。两种类型的进程的调度规矩也不一样,咱们分别来说。
首先是实时进程的调度规矩。Linux 系统对实时进程的调度战略有两种:先进先出(SCHED_FIFO)和循环(SCHED_RR)。Android 只运用了 SCHED_FIFO 这一战略,所以咱们首要介绍 SCHED_FIFO 。当系统运用先进先出的战略来调度进程时,假如某个进程占有 CPU 时刻片,此刻没有更高优先级的实时进程抢占 CPU,或该进程自动让出,那么该进程就始终保持运用 CPU 的状况。这种战略会进步进程运转的持续时刻,削减被打断或被切换的次数,所以呼应更及时。Android 中的 AudIO、SurfaceFlinger、Zygote 等系统中心进程都是实时进程。
而非实时进程也称为一般进程,针对一般进程,Linux 系统则采用了一种彻底公正调度算法来完成对进程的切换调度,咱们能够不需求知道这一算法的完成细节,但需求了解它的原理。在彻底公正调度算法中,进程的优先级由 nice 值表明,nice 值越低代表优先级越大,可是调度器并不是直接依据 nice 值的巨细作为优先级来进行使命调度的,当每次进程的时刻片履行完后,调度器就会寻觅一切进程中运转时刻最少的进程来履行。
既然调度器是依据进程的运转时刻来进行使命调度,那进程优先级即 nice 值的作用又表现在哪呢?实践上,这儿进程的运转时刻并不是真实的物理运转时刻,而是进行了加权计算的虚拟时刻,这个权值系数便是 nice 值,所以相同的物理时刻内,nice 值越低的进程所记录的运转时刻实践越少,运转时刻更少就更简单被调度器所挑选,优先级也就这样表现出来了。在 Android 中,除了部分中心进程,其他大部分都是一般进程。
了解了进程的调度原理,咱们再来了解一下进程的生命周期。
经过上图能够看到,进程或许有以下几种状况。而且运转、等候和睡眠这三种状况之间是能够相互转化的。
-
运转:该进程此刻正在履行。
-
等候:进程能够运转,但没有得到许可,由于 CPU 分配给另一个进程。调度器能够在下一次使命切换时挑选该进程。
-
睡眠:进程正在睡眠无法运转,由于它在等候一个外部事情。调度器无法在下一次使命切换时挑选该进程。
-
停止:进程停止。
知道了使命调度相关的原理后,怎样依据这些原理性知识来优化运用场景的速度呢?实践上,咱们对进程的优先级做不了太大的改动,即便改动了也产生不了太大的作用,可是前面提到了线程实践是轻量级的进程,相同遵从上面的调度原理和规矩,所以咱们真正落地的场景在线程的优化上。基于使命调度的原理,咱们能够衍生出这 2 类的优化思路:
-
进步线程的优先级:关于要害的线程,比方主线程,咱们能够进步它的优先级,来帮助咱们进步速度。除了直接进步线程的优先级,咱们还能够将要害线程绑定 CPU 的大核这一种特别的办法来进步该线程的履行功率。
-
削减线程创立或许状况切换的耗时:这一点能够经过在线程池中设置合理的常驻线程,线程保活时刻等参数来削减线程频繁创立或许状况切换的耗时。由于线程池十分重要,咱们后边会专门用一节课来具体讲解。
小结
在这一节中,咱们具体介绍了影响程序速度的三个实质要素,并基于这三个要素,介绍了许多衍生而来优化思路,这其实便是一种自下而上的功能优化思路,也便是从底层原理出发去寻觅计划,这样咱们在进行优化时,才干愈加全面和系统。
期望你经过这一节的学习,能对速度优化建立起一个系统的认知。当然,你或许会觉得咱们这一节介绍的优化思路太过简练,不必忧虑,在后边的章节中,咱们会基于 CPU、缓存和使命调度这三个维度,挑选出一些优化作用较好的计划,进行愈加深化的具体讲解。