首页 > 其他分享 >Android ART编译模式解析

Android ART编译模式解析

时间:2024-03-20 09:02:36浏览次数:16  
标签:dex2oat ART 代码 编译 AOT Android speed

前言

ART实际就是Android runtime的缩写,他是Android版本新的虚拟机

诞生

ART 使用预先 (AOT) 编译,并且从 Android 7.0(代号 Nougat,简称 N)开始结合使用 AOT、即时 (JIT) 编译和配置文件引导型编译。

区别

1.预先编译

ART模式与Dalvik模式最大的不同在于,在启用ART模式后,系统在安装应用的时候会进行一次预编译,在安装应用程序时会先将代码转换为机器语言存储在本地,这样在运行程序时就不会每次都进行一次编译了,执行效率也大大提升。并且对比dalvik,ART具有更严格的安装时验证。在安装时,ART 使用设备自带的 dex2oat 工具来编译应用。此实用工具接受 DEX 文件作为输入,并为目标设备生成经过编译的应用可执行文件。该工具应能够顺利编译所有有效的 DEX 文件。但是,一些后处理工具会生成无效文件,Dalvik 可以接受这些文件,但 ART 无法编译这些文件。

2.垃圾回收(GC)时优化

垃圾回收 (GC) 会耗费大量资源,这可能有损于应用性能,导致显示不稳定、界面响应速度缓慢以及其他问题。ART 通过以下几种方式对垃圾回收做了优化:

  1. 大多采用并发设计,具有一次 GC 暂停
  2. 并发复制,可减少后台内存使用和碎片
  3. GC 暂停的时间不受堆大小影响
  4. 在清理最近分配的短时对象这种特殊情况中,回收器的总 GC 时间更短
  5. 优化了垃圾回收的工效,能够更加及时地进行并行垃圾回收,这使得 GC_FOR_ALLOC 事件在典型用例中极为罕见
3.开发和调试方面的优化

ART 提供了大量功能来优化应用开发和调试。

支持采样分析器
一直以来,开发者都使用 Traceview 工具(用于跟踪应用执行情况)作为分析器。虽然 Traceview 可提供有用的信息,但每次方法调用产生的开销会导致 Dalvik 分析结果出现偏差,而且使用该工具明显会影响运行时性能。
ART 添加了对没有这些限制的专用采样分析器的支持,因而可更准确地了解应用执行情况,而不会明显减慢速度。KitKat 版本为 Dalvik 的 Traceview 添加了采样支持。

支持更多调试功能
ART 支持许多新的调试选项,特别是与监控和垃圾回收相关的功能。例如可以:

  1. 查看堆栈跟踪中保留了哪些锁,然后跳转到持有锁的线程。
  2. 询问指定类的当前活动的实例数、请求查看实例,以及查看使对象保持有效状态的参考。
  3. 过滤特定实例的事件(如断点)。
  4. 查看方法退出(使用“method-exit”事件)时返回的值。
  5. 设置字段观察点,以在访问和/或修改特定字段时暂停程序执行。

优化了异常和崩溃报告中的诊断详细信息
当发生运行时异常时,ART 会为您提供尽可能多的上下文和详细信息。ART 会提供 java.lang.ClassCastException、java.lang.ClassNotFoundException 和 java.lang.NullPointerException 的更多异常详细信息。(较高版本的 Dalvik 会提供 java.lang.ArrayIndexOutOfBoundsException 和 java.lang.ArrayStoreException 的更多异常详细信息,这些信息现在包括数组大小和越界偏移量;ART 也提供这类信息。)

一些基本名词

名称详情
dex文件App所有java源代码编译后生成众多class文件,由DX/D8,编译为一个/多个(multiDex)dex文件,由Android虚拟机编译执行
odex文件dex文件经过验证和优化后的产物,art下的odex文件包含经过AOT编译后的代码以及dex的完整内容,但Android8.0之后odex中的dex内容移动到了.vdex文件
art文件art下根据配置文件生成odex文件时同时生成.art文件,主要是为了提升运行时加载odex中热点代码的速度,包含了类信息和odex中热点方法的索引,运行App时会首先根据这个文件来加载odex中已经编译过的代码;执行过speed-profile命令的app才会生成.art文件
JIT编译(Just In Time)由于解释器方式运行太慢引入,对于频繁运行的热点代码(判定标准一般是在某个时间段内执行次数达到某个阈值)进行实时编译(在ART下以方法为粒度)执行,并且缓存JIT编译后的代码在内存中用于下次执行。由于以方法为粒度(ArtMethod)进行编译,JIT编译较于解释器可以生成效率更高的代码,运行更快
AOT编译(Ahead-Of-Time)应用安装时全量编译所有代码为本地机器码,运行时直接执行机器码。

JIT编译与AOT编译对比
JIT编译
优势:
1.能够根据当前硬件状况实时编译生成最优机器指令(ps. AOT 也能够作到,在用户使用是使用字节码根据机器状况在作一次编译)
2.能够根据当前程序的运行状况生成最优的机器指令序列
3.当程序须要支持动态连接时,只能使用 JIT
4.能够根据进程中内存的实际状况调整代码,使内存可以更充分的利用
劣势:
1.编译必须要占用线程资源,导致卡顿
2.因为编译时间须要占用运行时间,对于某些代码的编译优化不能彻底支持,须要在程序流畅和编译时间之间作权衡
3.在编译准备和识别频繁使用的方法须要占用时间,使得初始编译不能达到最高性能
4.由于 JIT Code Cache 是内存缓存,因此每次运行都需要重新编译

AOT编译
优势:
1.在程序运行前编译,能够避免在运行时的编译性能消耗和内存消耗
2.能够在程序运行初期就达到最高性能
3.能够显著的加快程序的启动
劣势:
1.应用安装和系统升级之后的应用优化比较耗时(重新编译,把程序代码转换成机器语言)
2.优化后的文件会占用额外的存储空间(缓存转换结果)

运作方式

Android 4.4~7.0

最开始ART只采用AOT编译,在App安装时就编译所有代码存储在本地,打开App直接运行,这样做的优点是应用运行速度变快,缺点也很明显,App安装时间明显变长,而且占用存储空间较大。

Android 7.0

Android N之后对于ART进行改动,重新引入了JIT编译,结合使用AOT/JIT混合编译,主要机制如下:
安装时不进行任何编译,前几次运行仅通过解释器解释运行,同时对热点代码进行JIT编译,并将这些代码的相关信息记录在一个配置文件里(data/misc/profile/cur/0/…)
设备处于空闲和充电状态时,编译守护进程读取配置文件对热点代码进行AOT编译并写入到app对应的odex文件中
再次启动应用后优先使用AOT编译过的代码,否则使用解释器+JIT编译,重复这个过程
对于一些庞大的APP,比如淘宝,有些功能可能你一辈子都不会用到,根据上述策略这部分代码就不会被编译保存,从而减少了存储空间的占用。另外,在系统升级时也避免了全量编译所有现存应用造成的时间空间消耗。

Android 8.0

Android 8.0引入了.vdex文件,它里面包含 APK 的未压缩 DEX 代码,以及一些用于加快验证速度的元数据。怎么理解呢?这里我们需要补充一下对dex文件的编译配置和系统ROM中各类应用的默认编译方式:
编译选项(compiler filters):

  1. verify:只对 DEX 文件进行代码验证,校验各部分合法性。
  2. quicken:在verify的基础上优化一些 DEX 指令,提升解释器性能。
  3. speed:在verify的基础对所有方法进行 AOT 编译。
  4. speed-profile:在verify的基础对配置文件中列出的方法(热点方法)进行 AOT 编译。

系统ROM中各类应用默认编译方式:

  1. 启动类路径代码(用于启动系统的部分代码):默认使用 speed 编译过滤器进行编译
  2. 系统服务器代码(比如电量、多媒体服务代码):默认使用 speed 编译过滤器进行编译
  3. 产品专属的核心应用(比如goole服务框架):默认使用 speed 编译过滤器进行编译
  4. 所有第三方应用:默认使用 quicken 编译过滤器进行编译

所有第三方应用最开始都是通过quiken模式只进行了dex校验和一些指令优化,.vdex文件存放的就是经过校验后的dex代码,以便在对热点代码进行AOT编译时避免重复验证,加快速度。

混合编译的优势

  1. 应用安装时间过长;在 N 之前,应用在安装时需要对所有 ClassN.dex 做 AOT 机器码编译,类似微信这种比较大型的 APP 可能会耗时数分钟。但是往往我们只会使用一个应用 20% 的功能,剩下的 80% 我们付出了时间成本,却没带来太大的收益。
  2. 降低占 ROM 空间;同样全量编译 AOT 机器码,12M 的 dex 编译结果往往可以达到 50M 之多。只编译用户用到或常用的 20% 功能,这对于存储空间不足的设备尤其重要。
  3. 提升系统与应用性能;减少了全量编译,降低了系统的耗电。在 boot.art 的基础上,每个应用增加了 base.art (这块后面会详细解析), 通过预加载与缓存提升应用性能。
  4. 快速的系统升级;以往厂商 ota 时,需要对安装的所有应用做全量的 AOT 编译,这耗时非常久。事实上,同样只有 20% 的应用是我们经常使用的,给不常用的应用,不常用的功能付出的这些成本是不值得的。

综合下来,混合编译的模式因为在安装的时候不需要进行编译,安装速度会很快。在以后运行app时,会抓取热点数据进行存储并执行AOT编译优化体验。所以在前几次打开应用时速度会相对较慢,在操作次数多了后性能将会跟上。
过滤器
ART 如何编译 DEX 代码还有个compile filter以参数的形式来决定:从 Android O 开始,有四个官方支持的过滤器:

verify:只运行 DEX 代码验证。
quicken:运行 DEX 代码验证,并优化一些 DEX 指令,以获得更好的解释器性能。
speed-profile:运行 DEX 代码验证,并对配置文件中列出的方法进行 AOT 编译。
speed:运行 DEX 代码验证,并对所有方法进行 AOT 编译。

verify 和quicken 他俩都没执行编译,之后代码执行需要跑解释器。而speed-profile 和 speed 都执行了编译,区别是speed-profile根据profile记录的热点函数来编译,属于部分编译,而speed属于全编。
所以
执行效率上:
verify < quicken < speed-profile < speed
编译速度上:
verify > quicken > speed-profile > speed

查看编译模式

在命令行中输入adb getprop | grep pm.dex,可以查看当前手机在不同场景下的默认编译模式
一般用到的主要是这几个:

  • ab-ota:系统升级,这里是使用A/B系统升级方式,也叫做无缝更新,A/B系统升级,顾名思义是有两个系统,在磁盘上开辟两个存储空间A/B存储空间,在升级过程中保证有一个可以正常运行的系统
  • bg-dexopt:后台编译
  • boot-after-ota:启动后升级
  • cmdline:命令行
  • first-boot:首次开机安装,google建议设置这个,避免开机的时间过长
  • inactive:不活跃的。这个不活跃状态也就是idle,判定是在frameworks/base/core/res/res/config.xml中有一个节点config_jobSchedulerInactivityIdleThreshold,默认值是1860000毫秒,也就是31分钟。31分钟处于非活动状态就会被认为是idle。
  • install:安装,设置为speed-profile可以在后续使用中提高应用启动时间,但是如果没有提供配置文件或者配置文件为空,那么启动时间会与verify没有区别
    那么这个设置是在哪里进行配置的呢?
    build/make/target/product/runtime_libart.mk
# The install filter is speed-profile in order to enable the use of
# profiles from the dex metadata files. Note that if a profile is not provided
# or if it is empty speed-profile is equivalent to (quicken + empty app image).
# Note that `cmdline` is not strictly needed but it simplifies the management
# of compilation reason in the platform (as we have a unified, single path,
# without exceptions).
PRODUCT_SYSTEM_PROPERTIES += \
    pm.dexopt.post-boot?=extract \
    pm.dexopt.install?=speed-profile \
    pm.dexopt.install-fast?=skip \
    pm.dexopt.install-bulk?=speed-profile \
    pm.dexopt.install-bulk-secondary?=verify \
    pm.dexopt.install-bulk-downgraded?=verify \
    pm.dexopt.install-bulk-secondary-downgraded?=extract \
   pm.dexopt.bg-dexopt?=speed-profile \
    pm.dexopt.ab-ota?=speed-profile \
    pm.dexopt.inactive?=verify \
    pm.dexopt.cmdline?=verify \
    pm.dexopt.shared?=speed

在这个文件里会提供一个没有frameworks的安装环境
User 版本会预先提取有source code的APK的odex文件,因为有source code的APK在 Android.mk中都会通过include $(BUILD_PACKAGE)来编译,会调用到package.mk来提取odex
但User版本不会预先提取通过prebuilt方式预置的APK的odex文件,因为采用prebuilt方式预置 APK是只有APK文件而没有source code时在Android.mk中通过 include $(BUILD_PREBUILT) 预置APK,代码中原本的prebuilt.mk中不会提取odex
user版本默认实现了apk预编译优化.如何实现不生成odex? 以下两种方式皆可

  1. 通过修改build/make/core/board_config.mk中WITH_DEXPREOPT := false
  2. 在APK的Android.mk文件中添加 LOCAL_DEX_PREOPT := false 来关闭预优化
Dex2oat 选项

请注意,这些选项在设备编译期间以及预先优化期间都会影响 dex2oat,但是前面讨论的大多数选项都只会影响预先优化。
在 dex2oat 编译启动映像时对其进行控制:

  • dalvik.vm.image-dex2oat-Xms:初始堆大小
  • dalvik.vm.image-dex2oat-Xmx:最大堆大小
  • dalvik.vm.image-dex2oat-filter:编译过滤器选项
  • dalvik.vm.image-dex2oat-threads:要使用的线程数

在 dex2oat 编译除启动映像之外的所有内容时对其进行控制:

  • dalvik.vm.dex2oat-Xms:初始堆大小
  • dalvik.vm.dex2oat-Xmx:最大堆大小
  • dalvik.vm.dex2oat-filter:编译过滤器选项

Android 6.0 之前的版本提供了一个适用于编译除启动映像之外的所有内容的附加选项:

  • dalvik.vm.dex2oat-threads:要使用的线程数

从 Android 6.1 开始,该选项变成了两个适用于编译除启动映像之外的所有内容的附加选项:

  • dalvik.vm.boot-dex2oat-threads:启动时要使用的线程数
  • dalvik.vm.dex2oat-threads:启动后要使用的线程数

Android 7.1 及之后的版本提供了两个选项来控制编译除启动映像之外的所有内容时的内存使用方式:

  • dalvik.vm.dex2oat-very-large:停用 AOT 编译的最小总 dex 文件大小(以字节为单位)
  • dalvik.vm.dex2oat-swap:使用 dex2oat 交换文件(用于低内存设备)

不应减小用于控制 dex2oat 初始堆大小和最大堆大小的选项数值,因为它们可能会限制可对哪些应用进行编译。
从 Android 11 开始,我们提供了 3 个 CPU 亲和性选项,通过这些选项,编译器线程可以限定在特定的一组 CPU 上:

  • dalvik.vm.boot-dex2oat-cpu-set:在启动时运行 dex2oat 线程的 CPU
  • dalvik.vm.image-dex2oat-cpu-set:在编译启动映像时运行 dex2oat 的 CPU
  • dalvik.vm.dex2oat-cpu-set:在启动后运行 dex2oat 线程的 CPU
单独APP安装模式

搜索app包下device.mk文件查找PRODUCT_DEXPREOPT_SPEED_APPS属性
例如device/google/coral/device.mk文件下就有这样一个属性

# Preopt SystemUI
PRODUCT_DEXPREOPT_SPEED_APPS += SystemUIGoogle  # For internal
PRODUCT_DEXPREOPT_SPEED_APPS += SystemUI  # For AOSP

单独为systemUI设置了speed编译模式

标签:dex2oat,ART,代码,编译,AOT,Android,speed
From: https://blog.csdn.net/u010345983/article/details/136330291

相关文章

  • android App启动流程三-Activity启动流程
    上一篇我们介绍了从App的进程创建到Application启动执行,今天我们继续深入学习一下,Activity的启动流程。realStartActivityLocked我们接着上一篇,从ActivityTaskManagerService.attachApplication函数看起,最终发现会执行到ActivityTaskSupervisor.realStartActivityLocked方法......
  • m基于OFDM+QPSK和LDPC编译码以及MMSE信道估计的无线图像传输matlab仿真,输出误码率,并
    1.算法仿真效果matlab2022a仿真结果如下:   2.算法涉及理论知识概要       无线图像传输在现代通信系统中扮演着至关重要的角色。为了满足高质量、高可靠性的传输需求,研究者们不断探索各种先进的编码、调制和信道估计技术。OFDM、QPSK、LDPC和MMSE信道估计就是其......
  • AOSP平台编写Android-ebpf程序(tracepoint)的一些map定义和使用问题,导致map和prog无法
     前言本片文章并不主要讲解在AOSP平台ebpf程序的整个编写流程,只是一些的map的定义使用问题,如有需要可查看,aosp平台的整个下载流程,以及简单的程序的编译和如何push到手机运行,这位up是我在ebpf领域探索的领路人,本站ID:LiujiaHuan13,如果有需要up本人后面会考虑写一篇aosp程序书写......
  • Google Earth Engine——如何实现裁剪后研究区影像的批量下载(以NDVI为例)
    简介GEE云平台(GoogleEarthEngine)是一个强大的云平台,提供了丰富的地理数据和计算资源,用于进行地理数据分析和处理。在GEE平台上,可以实现对研究区影像的单景影像(以NDVI为例)的批量下载。下面是具体的过程:1.登录GEE云平台并初始化首先,需要登录GEE云平台(https://earthengine.g......
  • 西门子 S7-200 SMART 系列六:手把手教你配置S7-200 SMART Modbus RTU
    s7-200smartModbus主站通讯一Modbus通讯简介在工业领域,Modbus无处不在,无论你是否用过,大概你肯定听过,那么Modbus是什么呢?Modbus是Modicon公司于1979年为使用可编程逻辑控制器通信而发表,现在已经发展成为工业领域标准通讯协议之一,广泛应用于PLC与设备、仪表之间进行数......
  • WebStorm 怎么编译 Vue 工程
    最近一些工程项目,需要用到Vue,只能从头学一下。目前使用的WebStorm,但是不知道怎么编译,还要下载Node.js,对这个不熟悉。最终找到了编译方法:记录一下1.打包方法1.1在工程里面,package.json,点击右键,选择显示npm脚本。然后点击build1.2菜单-->视图-->工具窗口-->npm,然......
  • 一文说透Linux编译特定内核版本的方法(ubuntu和树莓派)
    更多内容在在做开发的时候,我们可能会针对某个内核版本进行驱动的编写。这个时候就需要把版本编译到这个特定的内核版本。本文介绍ubuntu和树莓派两种环境系统的内核编译方式Ubuntu:已编译到5.9.0内核为例1将内核安装包和内核配置config放到虚拟机或PC机下2更新apt源,并安......
  • Android 圆形进度条ProgressBar实现固定进度
    原文:Android圆形进度条ProgressBar实现固定进度-Stars-One的杂货小窝之前遇到一个问题,发现Android里的圆形进度条无法固定一个进度,记录一下解决方法探究假设我们在xml中这样写:<?xmlversion="1.0"encoding="utf-8"?><FrameLayoutxmlns:android="http://schemas.and......
  • 亚洲唯一!京东荣获2024年度Gartner供应链技术创新奖背后的创新探索
    导语:2月14日晚间,Gartner公布了2024年度GartnerPoweroftheProfession供应链大奖,京东集团荣获供应链技术创新奖,成为获得该奖项的唯一亚洲企业。GartnerPoweroftheProfession供应链奖项已经举办十年,是衡量企业供应链创新能力的国际权威奖项。据悉,入围决赛的共有5家企业,另外4......
  • Qt 编译qt-material-widgets皮肤的方法
    编译qt-material-widgets皮肤的方法首先qt-material-widgets的源代码直接拉下来是无法编译的,我们只能根据报错一步步寻找如何编译。1.必须先编译components项目得到lib文件2.然后编译examples,但是发现编译失败3.因为在examples,pro默认为linux库,修改其中内容,libcomponents.a......