购物网站起名,小程序网站,室内装修设计师工资一般多少钱,网站建设需求文档编写目的作者#xff1a;helson赵子健 应用的速度优化是我们使用最频繁#xff0c;也是应用最重要的优化之一#xff0c;它包括启动速度优化#xff0c;页面打开速度优化#xff0c;功能或业务执行速度优化等等#xff0c;能够直接提升应用的用户体验。因此#xff0c;只要是 An… 作者helson赵子健 应用的速度优化是我们使用最频繁也是应用最重要的优化之一它包括启动速度优化页面打开速度优化功能或业务执行速度优化等等能够直接提升应用的用户体验。因此只要是 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 是因为这其实只是序号称呼上的不同而已你也可以理解成 L1L3 是 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 的大核这一种特殊的方式来提高该线程的执行效率。 减少线程创建或者状态切换的耗时这一点可以通过在线程池中设置合理的常驻线程线程保活时间等参数来减少线程频繁创建或者状态切换的耗时。因为线程池非常重要我们后面会专门用一节课来详细讲解。
小结
在这一节中我们详细介绍了影响程序速度的三个本质因素并基于这三个因素介绍了许多衍生而来优化思路这其实就是一种自下而上的性能优化思路也就是从底层原理出发去寻找方案这样我们在进行优化时才能更加全面和体系。
Android 学习笔录
Android 性能优化篇https://qr18.cn/FVlo89 Android 车载篇https://qr18.cn/F05ZCM Android 逆向安全学习笔记https://qr18.cn/CQ5TcL Android Framework底层原理篇https://qr18.cn/AQpN4J Android 音视频篇https://qr18.cn/Ei3VPD Jetpack全家桶篇内含Composehttps://qr18.cn/A0gajp Kotlin 篇https://qr18.cn/CdjtAF Gradle 篇https://qr18.cn/DzrmMB OkHttp 源码解析笔记https://qr18.cn/Cw0pBD Flutter 篇https://qr18.cn/DIvKma Android 八大知识体https://qr18.cn/CyxarU Android 核心笔记https://qr21.cn/CaZQLo Android 往年面试题锦https://qr18.cn/CKV8OZ 2023年最新Android 面试题集https://qr18.cn/CgxrRy Android 车载开发岗位面试习题https://qr18.cn/FTlyCJ 音视频面试题锦https://qr18.cn/AcV6Ap