Android 8.0 ART 改进

使用集合让一切井井有条 根据您的偏好保存内容并对其进行分类。

Android 运行时 (ART) 在 Android 8.0 版本中得到了显着改进。下面的列表总结了设备制造商在 ART 中可以期待的增强功能。

并发压缩垃圾收集器

正如在 Google I/O 上宣布的那样,ART 在 Android 8.0 中具有新的并发压缩垃圾收集器 (GC)。每次 GC 运行和应用程序运行时,此收集器都会压缩堆,处理线程根时只有一个短暂的暂停。以下是它的好处:

  • GC 总是压缩堆:与 Android 7.0 相比,堆大小平均减少 32%。
  • 压缩支持线程本地碰撞指针对象分配:分配速度比 Android 7.0 快 70%。
  • 与 Android 7.0 GC 相比,H2 基准测试的暂停时间减少了 85%。
  • 暂停时间不再随堆大小而变化;应用程序应该能够使用大堆而不用担心卡顿。
  • GC 实现细节 - 阅读障碍:
    • 读取障碍是为读取的每个对象字段所做的少量工作。
    • 这些在编译器中进行了优化,但可能会减慢某些用例。

循环优化

ART 在 Android 8.0 版本中采用了多种循环优化:

  • 边界检查消除
    • 静态:范围在编译时被证明在界限内
    • 动态:运行时测试确保循环保持在界限内(否则 deopt)
  • 感应变量消除
    • 去除死感应
    • 用封闭式表达式替换仅在循环之后使用的归纳
  • 循环体内的死代码消除,删除整个死循环
  • 强度降低
  • 循环变换:反转、互换、拆分、展开、单模等。
  • SIMDization(也称为矢量化)

循环优化器驻留在 ART 编译器中它自己的优化过程中。大多数循环优化类似于其他地方的优化和简化。一些优化以比通常更精细的方式重写 CFG 带来了挑战,因为大多数 CFG 实用程序(参见 nodes.h)专注于构建 CFG,而不是重写。

类层次分析

Android 8.0 中的 ART 使用类层次结构分析 (CHA),这是一种编译器优化,可根据分析类层次结构生成的信息将虚拟调用虚拟化为直接调用。虚拟调用很昂贵,因为它们是围绕 vtable 查找实现的,并且它们需要一些依赖负载。虚拟呼叫也不能内联。

以下是相关增强功能的摘要:

  • 动态单实现方法状态更新 - 在类链接时间结束时,当 vtable 已填充时,ART 对超类的 vtable 进行逐项比较。
  • 编译器优化 - 编译器将利用方法的单一实现信息。如果方法 A.foo 设置了单一实现标志,编译器会将虚拟调用去虚拟化为直接调用,并因此进一步尝试内联直接调用。
  • 编译代码失效 - 同样在类链接时间结束时更新单实现信息时,如果先前具有单实现但该状态现在已失效的方法 A.foo,则所有编译代码都取决于方法 A. foo 具有单一实现需要使其编译的代码无效。
  • 反优化 - 对于堆栈上的实时编译代码,将启动反优化以强制无效的编译代码进入解释器模式以保证正确性。将使用一种新的去优化机制,它是同步和异步去优化的混合体。

.oat 文件中的内联缓存

ART 现在采用内联缓存并优化存在足够数据的调用站点。内联缓存功能将额外的运行时信息记录到配置文件中,并使用它来为提前编译添加动态优化。

敏捷布局

Dexlayout 是 Android 8.0 中引入的一个库,用于分析 dex 文件并根据配置文件重新排序它们。 Dexlayout 旨在使用运行时分析信息在设备上的空闲维护编译期间重新排序 dex 文件的部分。通过将经常一起访问的 dex 文件的部分组合在一起,程序可以从改进的局部性获得更好的内存访问模式,节省 RAM 并缩短启动时间。

由于当前只有在应用程序运行后才能获得配置文件信息,因此在空闲维护期间将 dexlayout 集成到 dex2oat 的设备上编译中。

Dex 缓存删除

直到 Android 7.0,DexCache 对象拥有四个大数组,与 DexFile 中某些元素的数量成正比,即:

  • 字符串(每个 DexFile::StringId 一个引用),
  • 类型(每个 DexFile::TypeId 一个引用),
  • 方法(每个 DexFile::MethodId 一个本地指针),
  • 字段(每个 DexFile::FieldId 一个本地指针)。

这些数组用于快速检索我们之前解析的对象。在 Android 8.0 中,除方法数组外,所有数组均已移除。

口译员表现

解释器性能在 Android 7.0 版本中显着提高,引入了“mterp” - 一种具有用汇编语言编写的核心获取/解码/解释机制的解释器。 Mterp 仿照快速 Dalvik 解释器,支持 arm、arm64、x86、x86_64、mips 和 mips64。对于计算代码,Art 的 mterp 与 Dalvik 的快速解释器大致相当。但是,在某些情况下,它可能会显着(甚至是显着)慢:

  1. 调用性能。
  2. 字符串操作,以及其他在 Dalvik 中被视为内在函数的重度用户。
  3. 更高的堆栈内存使用率。

Android 8.0 解决了这些问题。

更多内联

从 Android 6.0 开始,ART 可以内联同一个 dex 文件中的任何调用,但只能内联来自不同 dex 文件的叶方法。这种限制有两个原因:

  1. 从另一个 dex 文件内联需要使用另一个 dex 文件的 dex 缓存,这与同一个 dex 文件内联不同,它可以重复使用调用者的 dex 缓存。对于静态调用、字符串加载或类加载等一些指令,编译代码中需要 dex 缓存。
  2. 堆栈映射仅对当前 dex 文件中的方法索引进行编码。

为了解决这些限制,Android 8.0:

  1. 从编译的代码中删除 dex 缓存访问(另请参阅“Dex 缓存删除”部分)
  2. 扩展堆栈映射编码。

同步改进

ART 团队调整了 MonitorEnter/MonitorExit 代码路径,并减少了我们对 ARMv8 上传统内存屏障的依赖,并尽可能用更新的(获取/释放)指令替换它们。

更快的本机方法

使用@FastNative@CriticalNative注解可以更快地调用Java Native Interface (JNI)。这些内置的 ART 运行时优化可加快 JNI 转换并替换现在已弃用的!bang JNI表示法。注释对非本机方法没有影响,并且仅可用于引导类bootclasspath上的平台 Java 语言代码(无 Play 商店更新)。

@FastNative注解支持非静态方法。如果方法将jobject作为参数或返回值访问,请使用此选项。

@CriticalNative注解提供了一种更快的方式来运行本机方法,但有以下限制:

  • 方法必须是静态的——没有参数、返回值或隐式this的对象。
  • 只有原始类型被传递给本机方法。
  • 本机方法在其函数定义中不使用JNIEnvjclass参数。
  • 该方法必须使用RegisterNatives ,而不是依赖动态 JNI 链接。

@FastNative可以将本机方法性能提高多达 3 倍, @CriticalNative CriticalNative 可以提高多达 5 倍。例如,在 Nexus 6P 设备上测量的 JNI 转换:

Java 本机接口 (JNI) 调用执行时间(以纳秒为单位)
常规 JNI 115
!bang JNI 60
@FastNative 35
@CriticalNative 25