回顾_初识性能优化及工具|青训营笔记

217 阅读13分钟

这是我参与「第四届青训营 」笔记创作活动的的第8天

为什么要做性能优化?

  1. 性能优化带来体验的改善
  2. 进而帮助业务指标的提升

image-20220831203016207

  1. 另外分享一个想法,从硬件的范围看 1980到现在 x86 偏pc 服务端的处理器单核性能性能提升的,早期飞快,到最近缓慢 再看arm平台,最近比intel好很多,两方面的因素

    1. arm持续稳定的迭代架构,引入了很多优化,提升 二级缓存,流水线上 乱序执行,多发射硬件的分支预设单元

    2. 还有半导体工艺提升,台积电,我们可以看到迭代,架构和工艺的双重提升最近,再看曲线比之前斜率放缓

image-20220831203234680

  1. 从更长的时间范围来看:多核带来的提升取决于可以真正并行执行的部分(受限于阿姆达尔定律)

    多核性能的提升,受限于并行任务之间需要去串行执行或同步执行的时间占比。

image-20220831203431623

  1. 未来? 未来会有新的材料和工艺来驱动芯片性能的进一步提升,但是目前还没有成熟

    移动处理器还受到电池技术的限制

    软件的性能优化仍可持续带来提升

性能优化是什么?

目标

image-20220831204135065

快:启动的速度,响应速度等等;

稳:闪退,异常等等;

省:存储空间,功耗等等

分类

流畅性优化快-极致的响应与流畅的体验
资源优化省-最小负载带来最大的收益
稳定性优化稳-稳定的实现,减少不必要打断
系统级优化拓展-底层booster

1. 流畅性优化

首先需要了解Android的线程结构

image-20220831204545109

事件影响因素
System Events关闭某些页面通知系统,相应回调会通知系统事件
Input Events如我们的手机屏幕,点击滑动等操作
Application启动全局SDK等等,我们内部处理,肯定越早越好
Services去执行那些不需要和用户交互而且还要求长期运行的任务
Alarm定时事件(Timer),比如说消息轮训,倒计时等
界面是如何刷新的?

image-20220831211920727

为什么16ms会发出一次VSYNC信号触发对UI进行渲染?

这是因为大多数的Android显示屏幕是以每秒60帧来刷新的(也就是60Hz)。

一帧可以看做是一张的独立图片,

60帧每秒就意味着:16ms=1000/60Hz,相当于60fps。这就是上面说的16ms

卡顿感知如何产生?
  1. 原因:你的主线程代码中,存在很重的操作,影响了输入事件的响应,输入事件无法及时响应 表现:滑不动的情况。

    image-20220831212329137

  2. 原因:输入事件操作过度,一些事件分发,什么的都在拿到触摸事件之后操作,

    表现:滑一下卡一下

image-20220831215732891

  1. 最开始提到的分析的诸多因素在主线中

    表现:在观看短视频的时候,没有任何操作,突然卡顿了一下

    image-20220831215915987

如何解决卡顿?

将一些耗时的操作,放到其他线程去操作。

image-20220831220455332

扩展:

问题1:人的肉眼能感知到不卡顿的最低帧数是多少?

​ A. 25 B.30 C.40 D.60

答案:选A(来源于抖音内部测试)

问题2:如果没有vsync信号会有什么问题

A.画面撕裂(一半显示A,一半显示B)

B.画面抖动(绘制很多个,卡一下,又绘制很多个)

C.画面丢帧(绘制了一段,突然没有响应,然后跳到新的东西继续渲染)

D.画面闪烁(页面一直在闪烁)

答案:A画面撕裂

简单示意图如下

核心点在与数据交换的时机由谁来控制,数据交换的发生点应该是在屏幕渲染完一帧后,而不是cpu写入一帧数据后

所以,控制数据是否交换应该由屏幕来决定,但是计算机五大组成部分各司其职,

屏幕只是输出设备和输入设备(因为能触屏),

他不是控制器,如何控制数据的交换呢? 答案就是:VSYNC

image-20220831222805702

2. 资源优化

什么是资源?

资源:即Android手机的软件和硬件资源

通俗意义上应用依效的移动终端的有限资源和系统设置的数值,即功耗、存烤、流量、系统参数、CPU、内存等

Android端能做哪些资源类优化?

先来看一下用户对各个优化点的敏感度:

image-20220831223529263

除此之外,因为我们手机的内存是有限的,在启动APP的时候,这时候内存不足,就会调用GC清理后台应用

场景:当我们刚刷完抖音,去启动游戏的时候,在低内存手机中,我们就会感知启动较慢,就回去后台关闭抖音

image-20220831223601992

小结:

端侧场景:

抖音用户上传视频,用户自身录制可能会一下声音大声音小

但是我们刷视频的时候大部分情况下是没有的

这时候就是抖音在端侧做了音量归一化。

服务测资源:

CDN资源:保证了各地数据的流畅性

API流量:内部技术治理,常见场景就是比如说突发了一个事情,这时候一堆人来到了微博查看,但是却发现微博崩了,这是去抖音搜一搜相关资源,发一发短视频,发现抖音是正常的,这就是一个内部技术的区别

image-20220831224256998

带来的好处:

场景:抖音为什么是黑色的主题,目的之一就是降低功耗

下图包含每次运行的前 10 个数据点的样本,以及所有30 次测量的总体平均功耗。您可以很清楚地看到,在这种情况下,黑屏确实会减少手机使用的电量。在 Reddit Sync 中使用以黑色为主的界面时,总体功耗降低了41%。

image-20220831225003041

3. 稳定性优化

需要关注什么?

image-20220831225042517

Crash(崩溃),也就是我们常见的应用闪退。

ANR是(Application Not Responding ) 的缩写,即应用程序无响应。

如果 Android 应用的界面线程处于阻塞状态的时间过长,会触发App ANR错误。如果应用位于前台,系统会向用户显示一个对话框,ANR 对话框会为用户提供强行退出应用或等待的选项。

image-20220831225204938

4. 系统级别优化

从Android平台对性能影响比较大的feature出发,如下图

image-20220831225301837

节点原因
zram ksm 内存提升进入Android 主分支
主要是为了提供内存的使用率
如系统只给了我们10%的内存,那么我们就只能用10%嘛?
哪肯定不是的,就提出了内存压缩,让我们的使用率提高
Android Runtime(ART)2015年当时存在的一个现象:
当时打开一个应用Android普遍比IOS慢
引入了新的虚拟机
HMP scheduler当时技术的进步,出现了八核处理器但是技术却没有更上,
当时比较流行的一段话就是:一核 干活七核围观的现象
sdcardFS当时的一些手机存储还是比较低的,8G,16G
提出外置SD这个功能来解决这个问题
Cpuset group前后台切换
比如当前台应用切换到后台的时候,对于负载会减少,这时候就要限制
所以就提出了这个概念帮助大小核使用
SKGL & Vulkan替代opengl图形库,帮助图形渲染
F2FS碎片化处理,
之前华为说手机可以用两年不卡,就是运用的这个功能
ART: profile and image对ART进行优化,更精确的对代码进行精简
binder重构因为当时的谷歌有一套自研的IPC框架,但是体验一直不好
所以就重构了一套,使得binder并发性能得到大幅提升
Cloud profile可以通过google play下发已经预编译好的speed profile
不用打开再去计算
EAS 大小大小核调度器引入解决功耗和性能平衡的痛点
Generational Concurrent Copying GC2019引入分代的并行拷贝GC 被引入,提升GC性能
从什么角度去优化呢?

映射到软件栈上:app fram 都了解,虚拟机,核心库,kernel,芯片soc,更好利用这些,

这些模块怎么作用到我们的程序上,以及怎么影响我们的程序,

去更好的利用他们这些模块的一些能力来帮助我们提升性能,这就是系统级优化需要展开讨论的地方

image-20220831231323034

小结

本章我们通过以下几个问题去分析我们的稳定性优化

  1. 性能优化的分类?
  2. 流畅性指的是什么?
  3. 资源优化都涉及哪些资源?
  4. 为什么性能问题会导致稳定性问题?
  5. 除了程序自身优化以外我们还可以做什么?

最佳工具选型

1. 性能工具的必要性

a. 监控和优化相生相伴

b. 监控有攻也有防

c. 攻是为了发现现有问题,指导优化方向

d. 防是为了发现劣化问题,及时止损

e. 线上监控发现问题并聚合排序,线下监控作为线上辅助,并发版前置发现问题

2. 快速流畅性找出问题-GPU呈现模式

原理:系统通过记录每一帧的相关数据,然后通过图形的形式呈现

优点:无需二次开发,简单易用

缺点:井不完全准确,且无法明确指出造成卡顿问题的具体原因

image-20220831231720832

image-20220831231731739

3. 快速找出布局问题-过度绘制(抖音自研工具Layertool)

原理:通过遍历View Tree信息,输出View层级关系

优点:清楚明了,可以宏观感知ViewTree现状,也可以定制,帮助分析overdraw

缺点:还不能够清楚明确的分析出U的性能瓶颈

image-20220831231855546

3. 深入性能归因-初阶-CPU Profiler

原理:基于JVMTI

优点:完整的方法调用栈输出、支持Java、c、C++方法耗时检测、上手简单

缺点:性能损耗太大

image.png

4. 深入性能归因-中阶-Trace View

Instrument

虚拟监听函数入口回调,Enter/Exit/Unwind

耗时点:读时间、写数据到buffer、加锁等指令

Sample

通过定时抓取多次堆栈dff,近似确定函数的进入和退出时间

耗时点:堆栈diff、同instrument

间隔抓取堆栈的时间越长性能损耗越少,而越会导致短函数检测不到

image-20220831232143080

5. 深入性能归因-高阶-Systrace

ftrace: debugfs采集和读取trace数据,记录trace events

atrace: 用户侧的trace跟踪,聚合所有的trace event

系统级的Trace数据:锁监控等

image-20220831232309804

6. 抖音大杀器-btrace (aka rhea)-进阶

rhea-systrace:全函数插桩,自动生成Trace代码,对层数做限制,性能损耗50% rhea-mtrace:全函数插桩,抛弃systrace,自己统计函数耗时,最后数据展现同systrace rhea-atrace:优化systrace性能,聚合更多性能数据:类加载、Lock、10等

image-20220831232351808

7. 如何查出功耗问题-Battery Historian

系统级功耗检测,更详细的数据

image-20220831232421512

image-20220831232708304

如何优化_成为一个优秀的屠龙少年

现状分析_掌握问题根源,才能彻底解决问题

耗时成因

CPU Time: 循环,反射,序列化/反序列化,类解析 IO Wait: 1o操作,等待l0返回结果 IPC: Binder调用耗时 Lock Wait: 主线程是等锁状态,等待其他线程或者自己超时 CPU Schedule: 主线程是可执行状态,但是获取不到cPU时间片

image-20220831232858447

运行环境归因

  1. 根据耗时成因归类
  2. 根据运行所在线程环境采用不同的策略

image-20220831232954046

启动耗时归因

Cold Start:Create process、ContentProvider init、Application#Create、Aim for <3 seconds Warm Start:Activity#Create、Inflate view hierarchy、Aim for <1 seconds Hot Start:Activity#onStart

image-20220831233040717

渲染分析

根据渲染串行流程逐层分析

Choreographer VSync > UI Thread**>RenderThread>**Graphics

image-20220831233159158

同时需要考虑并行影响,如途中 Ul Thread中红色方块和Other Process / Thread中的橙色方块block task

image-20220831233400117

案例分析

问题:尝试描述一下一个布局从xml变成view对象的耗时点?

image-20220831233551929

解答:

A. IO

B. 类反射

C. View初始化

D. AssertManager资源锁

拆解View显示流程中的耗时成因

image-20220831233825385 耗时成因:xml 10、class反射、创建view、Asset资源大锁

那么如何去解决呢?

谷歌提供的UI构建的优化方案

官方解决方案:AsyncLayoutinflater

当用到一个布局先去异步Inflate,完成了之后在进行设置

img

抖音解决方案:

Andinflater:解决xm/性能问题外界方案x2C Legolnflate:高优先级的启动预加载方案 Asynclnflater:随时随地预加载,不与具体逻辑绑定,生命周期存活,自定义清理周期

数据请求+解析_优化

GSON解析优化,如果我们对使用的数据有复用,差距是十分大的

image-20220831234321167

image-20220831234402186

数据协议优化:json->protobuf

image-20220831234430429

对比

image-20220831234440373

渲染和布局优化方案

系统工具查看具体的overdraw情况,但并不能细化到具体的布局和位置,我们开发了LayerTool工具可以根据自定义需求过滤各个布局,然后在界面上提示对应的位置,可以有效的查找导致overdraw的布局。我们通过LayerTool工具查看哪些布局导致了过度绘制。然后我们可以逐个开始优化:

  • 移除不必要的背景图 :去除冗余背景

  • 修改不合理布局:精细化布局显示

  • 写高效合理的布局:随着状态的变换,及时调整布局的绘制背景

  • 移除默认的 Window 背景 :Activity其实是有一层默认背景windowBackground。如果需要做到极致的优化,可以考虑将windowBackground设置为空,但是同时需要处理好随之而来的各种问题。主要包括:把windowBackground设置为空后,如果有某些View没有设置背景,就会出现花屏/重影的现象。

渲染优化的异步化方案

如何彻底解决主线程的阻塞问题:

SurfaceView:采用独立的线程进行绘制和渲染,生命周期需要自己控制

Jetpack Compose:基于组合优于继承的思想,重新设计一套解耦的U框架

Litho:复杂UI下的高性能渲染框架

扁平化:Litho采用Yoga完成组件布局measure和layout,实现布局的扁平化

组件化:Litho 使用 Drawable作为Node绘制单元,实现了布局的细粒度复用和异步计算布局的能力

缺点:不支持现有逻辑,需要重新实现

img

img

总结

本节课我们通过从概念到例子的理解

  1. 现状分析?洞察程序内部的瓶颈,层层剥离,最终才能发现问题根源

  2. 经典案例: 入门:一个view的成长史 初阶:View 的构建优化 初阶:如何快速的填充View 初阶:View之间的组合优化 高阶:如何解决构建、填充、组合带来的主线程流畅问题->异步,多核并发

通过课上老师精彩的讲解 了解到了在Android中页面是如何去渲染的,我们利用工具去分析拆解问题,最后把问题解决!

参考资料

Android 客户端专场 学习资料三

萌新初学,本文为笔记,大佬若有更好的见解欢迎评论区留言