Android 性能优化之黑科技开道(二)

3. 其它可以黑科技优化的方向

3.1 核心线程绑定大核

3.1.1 定义

核心线程绑定大核的思路也很容易理解,现在的 CPU 都是多核的,大核的频率比小核要高不少,如果我们的核心线程固定运行在大核上,那么应用性能自然会有所提升。

核心线程指的是 UI 线程、RenderThread 线程,因为它们直观影响用户的感受,或者在具体项目中的其它特定线程,比如语音处理,为了有更快的处理结果,语音线程也是可以列为核心线程的。

3.1.2 查看设备是否有大小核

1. 可以通过/sys/devices/system/cpu/目录下的文件获取各个核的频率

2. 尝试了下正在开发的设备,它没有大小核之分,所有核的频率全都一样,如下:

3. 当然,我们可以将此判断写到代码中,由我们的 App 智能判断是否需要绑定大小核,并找出来大核线程是哪个,具体代码这里就不贴了,原理同上,需要注意下读取权限问题

3.1.3 绑定 CPU 核实现

1. 绑定大核是通过函数 sched_setaffinity 实现的。

extern "C" JNIEXPORT void JNICALL Java_com_zj_android_startup_optimize_StartupNativeLib_bindCore(JNIEnv env, *jobject /* this */, jint thread_id, jint core) {cpu_set_t mask;     // CPU  核的集合CPU_ZERO(&mask);    // 将mask置空CPU_SET(core, &mask);    // 将需要绑定的  cpu  核设置给mask,核为序列0,1,2,3……if (sched_setaffinity(thread_id, sizeof(mask), &mask) == -1) { // 将线程绑核LOG  ("bind thread %d to core %d fail", thread_id, core);} else {LOG  ("bind thread %d to core %d success", thread_id, core);}
}

2. 如上所示,sched_setaffinity 共有 3 个参数。

  • 参数 1 是线程的 id,如果为 0 则表示主线程。
  • 参数 2 表示 cpu 序列掩码的长度。
  • 参数 3 则表示需要绑定的 cpu 序列的掩码。

3. 以上是线程绑定大核的核心代码,可以看到我们还需要获取 RenderThread 的 id ,以及 cpu 大核的序列。

4. 应用中线程的信息记录在 /proc/pid/task 的文件中,通过解析 task 文件就可以获取当前进程的所有线程,而 cpu 大核序列也可以通过解析 /sys/devices/system/cpu 目录实现。

3.2 GC 抑制

3.2.1 什么是 GC 抑制

  1. 首先 GC,就是 Java 的垃圾回收,GC 抑制指的是在 App 启动阶段,不让系统做 GC 或者是将 GC 的频繁降低,以提高启动速度

  2. 此技术在 Android10 以上的系统已加入,所以这里讨论的是 在 Android10 以下的系统中添加此功能

3.2.2 Android10 中的 GC 抑制如何实现的

1. Java 的垃圾回收机制,在 Android 5.0 之后,ART 取代了 Dalvik,ART 虚拟机在垃圾回收的时候虽然没有像 Dalvik 一样 stop the world,但在启动阶段如果发生垃圾回收,GC 线程同样抢占了不少系统资源。

2. Google 也注意到启动阶段 GC 对启动速度的影响,并在 Android 10 之后做了一定的优化,详情可见如下提交:https://cs.android.com/android/_/android/platform/art/+/a98a28262f645d100e2dee9587e7822d35ade6f9 

3. 可以看出,基本思路是在 2s 内提高后台 GC 的阈值,减少启动阶段的 GC 次数,根据 Google 的测试,抑制 GC 后效果如下:

4. 可以看出,GC 次数明显减少,启动速度也有一定的提升。

3.2.3 我们的程序是否有必要进行 GC 抑制

1. 可以通过以下代码获取 gc 的次数与耗时,方便统计 gc 对启动耗时的影响,以评估是否有必要做 GC 抑制

Debug.getRuntimeStat("art.gc.gc-count") // gc 次数
Debug.getRuntimeStat("art.gc.gc-time")  // gc 耗时
Debug.getRuntimeStat("art.gc.blocking-gc-count") // 阻塞 gc 次数
Debug.getRuntimeStat("art.gc.blocking-gc-time") // 阻塞 gc 耗时 

在电视项目的首页查看 GC 的情况,结果如下,发现从启动到首页显示出来,GC 次数和时间都是比较高的值:

2. 另外,我在 profiler 工具中观察到我们的 GC 线程可以更直观的看到,不只是在启动的时候,后续它也会频繁大量的运行,如下:

3.2.4 GC 抑制实现

GC 工作的原理

GC 主要是通过 HeapTaskDaemon 线程实现的,这是一个守护线程,在 Zygote 线程启动后这个线程也就启动了,启动后主要做了以下工作:

  1. 从 HeapTaskDaemon.runInternal()方法开始一步步调用到 native 层的 task_processor.RunAllTasks() 方法。

  2. 当 TaskProcessor 中的 tasks 为空时,会休眠等待,否则会取出第一个 HeapTask 并执行其 Run 方法。

    而 HeapTask 的 Run 方法是一个虚函数,需要子类来实现。

class HeapTask : public SelfDeletingTask {
};class SelfDeletingTask : public Task {
};class Task : public Closure {
};class Closure {public:virtual ~Closure() { }// 定义 Run 虚函数virtual void Run(Thread* self) = 0;
};

HeapTask 就是垃圾回收的任务,有多个子类,比如最常见的 ConcurrentGCTask 就是其子类,在 Java 内存达到阈值时就会执行这个 Task,用于执行并发 GC。

GC 抑制方案:Native 层的 Hook

在了解了 HeapTaskDaemon 的执行流程之后,我们想到,如果启动时在 ConcurrentGCTask 的 Run 方法执行前休眠一段时间,不就可以实现 GC 抑制了吗?

而 Run 方法正好是虚函数,虚函数与 Java 中的抽象函数类似,留给子类去扩展实现多态。

虚函数和外部库函数一样都没法直接执行,需要在表中去查找函数的真实地址,那么我们是不是可以使用类似 PLT Hook 的思路,使用自定义函数的地址替换原有函数地址,实现 Hook 呢?

答案是肯定的,如上图所示,一个类中如果存在虚函数,那么编译器就会为这个类生成一张虚函数表,并且将虚函数表的地址放在对象实例的首地址的内存中。同一个类的不同实例,共用一张虚函数表的。

因此我们的主要思路如下:

  1. 启动时将虚函数表中的 Run 函数地址替换为自定义函数地址。

  2. 在自定义函数内部休眠一段时间,抑制 GC。

休眠完成后将虚函数表中的函数地址替换回来,避免影响后续执行。

3.3 字节码插桩与性能监控

3.3.1 性能监控的流程

基于性能问题,我们可以进行一个性能方面的监控,以达到随时了解情况,随时进行优化的目的。市场上有很多商业化的 APM 平台,比如著名的 NewRelic,还有国内的 听云、OneAPM 等等,还有我们自己也有性能监控平台。这些平台的工作流程如下:

  1. 首先在客户端(Android、iOS、Web 等)采集数据;

  2. 接着将采集到的数据整理上报到服务器;

  3. 服务器接收到数据后建模、存储、挖掘分析,让后将数据可视化,供用户使用。

其中客户端数据采集时使用字节码插桩比较方便快捷,并且具有较大的通用性

3.3.2 字节码插桩原理

字节码插桩的原理就是在 Android 打包的时候,通过 ASM 等框架将 Java 字节码,插入到特定位置上,达到自动加入某些重复代码的目的,也即是 AOP 编程,如下是 Android 打包的流程:

插桩入口

在打包过程中,会将所有 class 文件,包括第三方的 class 文件打包成一个或者多个 dex 文件。这其中涉及到两个很关键的环节:

javac:将 。java 格式的源代码文件编译成 class 文件;

dex: 将 class 格式的文件打包汇总,组成一个或者多个 dex 文件。

我们想要对字节码进行修改,只需要在 javac 之后 dex 之前遍历所有的字节码文件,并按照一定的规则过滤修改就好了,这里便是字节码插桩的入口。

那么我们到底如何介入打包过程,在 class 转换为 dex 文件的时候实现对字节码的修改呢?

答案是 transform api。Android Gradle Plugin 1.5.0 及以上版本,Google 官方提供了 transform api 作为字节码插桩的入口。我们只需要实现一个自定义的 Gradle Plugin,然后在编译阶段去修改字节码文件即可。

修改字节码

找到了插桩入口,接下来就要对字节码进行修改。对于字节码的修改,比较常用的框架有 Javassist 和 ASM。具体的使用就不进行介绍了,有框架使用的话,写字节码还是比较方便的。

4. 总结

本篇主要介绍了一些 Android 中实用的黑科技,包括 Hook 技术,线程自定义调整,GC 抑制,字节码插桩等,在电视版智家 App9.0 项目中已经验证了部分技术,还有一些技术正在规划中,后续将会逐步的提升我们的 App 性能。

最后,讨论一个问题,这些黑科技是"奇淫巧技"吗,还是合理合法的使用呢?

这里引用一篇文章中的原话:

国产定制安卓系统一直都在安卓版本号更新之前,领先不只一个身位。

以至于每次的安卓大版本更新像是在追授国产定制 Android 在 N 年前魔改的功勋,甚至像是在若干个发行版本选一个最好的方案作为整个 Android 生态的标准。

招安,才是最形象的解释。

参考:如何评价谷歌刚发布的 AOSP14,在 iOS 和鸿蒙的竞争下,安卓还有哪些第三方开发的系统亮点值得关注?

国内的 Android 黑科技一直是率先发展的,遍数国内 Android 技术圈走过的路程,从之前的插件化,到双开等,哪一个在当时不算是"奇淫巧技"呢,最后不都成了 Android 官方的标配了么,所以,大胆的探索去吧,能解决我们问题的技术就是好技术。

5. 参考

  1. 盘点 Android 常用 Hook 技术

  2. 如何优雅关闭 Android 日志输出

  3. Android 中如何 Hook 住 JNI 方法

  4. JNI 函数 Hook 实战

  5. 启动优化中的一些黑科技,了解一下~

  6. Android 性能监控系列一(原理篇)

  7. 如何评价谷歌刚发布的 AOSP14,在 iOS 和鸿蒙的竞争下,安卓还有哪些第三方开发的系统亮点值得关注?

6. 团队介绍

三翼鸟数字化技术平台-场景设计交互平台」主要负责设计工具的研发,包括营销设计工具、家电VR设计和展示、水电暖通前置设计能力,研发并沉淀素材库,构建家居家装素材库,集成户型库、全品类产品库、设计方案库、生产工艺模型,打造基于户型和风格的AI设计能力,快速生成算量和报价;同时研发了门店设计师中心和项目中心,包括设计师管理能力和项目经理管理能力。实现了场景全生命周期管理,同时为水,空气,厨房等产业提供商机管理工具,从而实现了以场景贯穿的B端C端全流程系统。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/638416.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

new[]与delete[]

(要理解之前关于new,delete的一些概念,看​​​​​​ CSDN) 引子: 相比new,new[]不仅仅是个数的增加,还有int大小记录空间的创建, 下图中错误的用模拟多个new来替代new[],释放步…

Git 原理及使用 (带动图演示)

文章目录 🌈 Ⅰ Git 安装🌙 01. Linux - centos 🌈 Ⅱ Git 工作区、暂存区和版本库🌙 01. 认识工作区、暂存区和版本库🌙 02. 使用 Git 管理工作区的文件 🌈 Ⅲ Git 基本操作🌙 01. 创建本地仓库…

Java客户端如何直接调用es的API

Java客户端如何直接调用es的API 一. 问题二. withJson 前言 这是我在这个网站整理的笔记,有错误的地方请指出,关注我,接下来还会持续更新。 作者:神的孩子都在歌唱 一. 问题 今天做项目的时候,想要直接通过java客户端调用es的api…

docker的安装以及docker中nginx配置

机器 test3 192.168.23.103 1机器初始化配置 1.1关闭防火墙,清空防火墙规则 systemctl stop firewalld iptables -F setenforce 01.2部署时间同步 yum install ntp ntpdate -y1.3安装基础软件包 yum install -y wget net-tools nfs-utils lrzsz gcc gcc-c make…

2023年网络安全行业:机遇与挑战并存

2023年全球网络安全人才概况 根据ISC2的《2023年全球网络安全人才调查报告》,全球的网络安全专业人才数量达到了550万,同比增长了8.7%。然而,这一年也见证了网络安全人才短缺达到了历史新高,缺口数量接近400万。尤其是亚太地区&am…

【Linux学习】Linux调试器-gdb使用

这里写目录标题 🌂背景🌂gdb使用🌂指令总结: 🌂背景 程序的发布方式有两种,debug模式和 release模式 其中,debug模式是可以被调试的,到那时release模式是不能被调试的; …

用Nest实现对数据库的增删改查~

概述 为了与 SQL和 NoSQL 数据库集成,Nest 提供了 nestjs/typeorm 包。Nest 使用TypeORM是因为它是 TypeScript 中最成熟的对象关系映射器( ORM )。因为它是用 TypeScript 编写的,所以可以很好地与 Nest 框架集成。 TypeORM 提供了对许多关系数据库的支…

数据库主从复制

一、主从复制概述 1、介绍: 主从复制是指将主数据库的 DDL 和 DML 操作写入到二进制日志中,将二进制日志传送到从库服务器,然后在从库上对这些日志重新执行(重做),从而使得从库和主库的数据保持同步。 M…

护眼台灯哪个牌子好?排名靠前的护眼台灯十大排名推荐!

护眼台灯哪个牌子好?目前,书客、松下、飞利浦等品牌备受关注。急需护眼的朋友,先不必焦虑。护眼台灯的选择,同样需要细致考虑,不是简单地亮起来就足够护眼。因为不当的光线可能对眼睛造成微妙而长远的伤害,…

怎样快速打造二级分销小程序

乔拓云是一个专门开发小程序模板的平台,致力于帮助商家快速上线自己的小程序。通过套用乔拓云提供的精美模板,商家无需具备专业的技术背景,也能轻松打造出功能齐全、美观大方的小程序。 在乔拓云的官网,商家可以免费注册账号并登录…

火力发电资质升级,河南企业申报周期一览

河南企业申报火力发电资质从丙级升级到乙级的周期,通常是一个涉及多个环节和因素的复杂过程。因此,具体的申报周期会因企业的实际情况、申报材料的准备情况、审批部门的工作效率等多种因素而有所差异。 一般来说,整个升级周期可能包含以下步骤…

【团体程序设计天梯赛】L2-052 吉利矩阵

思路: 直接回溯枚举每一个位置填的数,二维肯定是不方便的,我们转成一维,下标x从0到n*n-1。二维数组下标从0到n-1,在一维中下标为x的点在二维中对应行是x/n,列是x%n。 每个数最小能填的是0,最大…