Android S ART的变化

  • 1. Android compiler-filter的发展历程
  • 2. Profile与其中的Appimage
  • 3. Android S相对于Android R的变化


1. Android compiler-filter的发展历程

通过历代Android compiler-filter,可以发现Google在art这块的技术路线
Android M(6) Android N(7) Android O(8) Android P(9) Android Q(10) Android R(11) Android S(12) 这里讲一下常见的:

speed: 编译所有的方法,最大的性能(不过需要占用比较多的rom,曾经有一段时间是android默认的编译选项)
interpret-only:验证所有内容,只编译JNI stub (Android O开始已经给"quicken"替代, androidN曾经盛行一时,之前微信安装时间过长,曾经用这个来替代微信默认使用的speed模式)
quicken:在Android O开始启用,用于替代interpret-only(一代霸主,从Android O开始到默认安装(Android P给speed-profile替代)和编译选项,到Android R上都还是默认编译选项)
speed-profile:根据提供的profile文件,来执行speed(根据profile的方法来编译),最大化性能。(Android N已经出现,Android O做为bg dex也就是google当时宣传的越用越快方案, Android P变成了默认安装也开始布局playstore云端profile,Android Q playstore云端profile开始逐渐出现在各个机型中。从出现之初受到Google的钟爱,一直大力投入扩展使用方案)
verify:仅仅校验类 (这个一直都是fota,pm.dexopt.boot的首选项,在Android S之前和quicken有性能差异(编译范围小一点),在Android S这个和quicken合并,其实相当于之前的quicken)
everything-profile: 根据profile,编译所有可以编译的(有些三方应用如抖音,进入一段时间就会做这个,做dexopt过程中由于需要的资源比较多,在部分低端机型上这段时间会比较容易出现卡顿现象)

从Android的art发展历程,可以知道Google的技术路线,其实这部分路线也在头部几大手机厂商都有布局,后面讲到了就跟大家聊一下这些技术路线,
这里也大概提一下:

speed-profile 这块是大部分手机厂家发力点,如云端profile(国内没法用playstore,头部厂家自己的商店里做了类似功能)、三方dex(参考bg dex,只是触发时机不一样,最开始在华为Android O机型中出现,后面很多厂家都有做,在Android R mtk自己都集成了类似功能)、上传apk的时候可以开发者自己提供profile文件

后面这块应用我会开篇讲解,让后来者可以不绕弯路,目前很多技术都已经相当成熟

2. Profile与其中的Appimage

针对app的优化,在Android O引入AOT预编译为了解决运行慢的问题,后面出现的appimage、profile都是用来完善这套体系了

JIT Just-in-time

在程序运行时选择将最频繁执行的方法编译成本地代码。运行时才进行本地代码编译而不是在程序运行前进行编译(用 C 或?C++?编写的程序正好属于后一情形),保证了可移植性的需求。有些 JIT 编译器甚至不使用解释程序就能编译所有的代码,但是这些编译器仍然通过在程序执行时进行一些操作来保持 Java 应用程序的可移植性。动态地编译 Java 程序有一些重要的优点,甚至能够比静态编译语言更好地生成代码,现代的 JIT 编译器常常向生成的代码中插入挂钩以收集有关程序行为的信息,以便如果要选择方法进行重编译,就可以更好地优化动态行为。但是,动态编译确实具有一些缺点因为识别频繁执行的方法以及编译这些方法需要时间,所以应用程序通常要经历一个准备过程,在这个过程中性能无法达到其最高值。在这个准备过程中出现性能问题有几个原因。首先,大量的初始编译可能直接影响应用程序的启动时间。不仅这些编译延迟了应用程序达到稳定状态的时间(想像 Web?服务器经历一个初始阶段后才能够执行实际有用的工作),而且在准备阶段中频繁执行的方法可能对应用程序的稳定状态的性能所起的作用也不大。如果 JIT 编译会延迟启动又不能显著改善应用程序的长期性能,则执行这种编译就非常浪费。

AOT Ahead-of-Time

即预编译或静态编译,目的在于避免 JIT 编译器的运行时性能消耗或内存消耗,或者避免解释程序的早期性能开销。提前编译 Java 程序可以提高性能,尤其是在不能将动态编译器作为有效解决方案的环境中。可以通过谨慎地使用 AOT 编译代码加快应用程序启动,因为虽然这种代码通常比 JIT 编译代码慢,但是却比解释代码快很多倍。

profile与其中的appimage

Profile:结合AOT,因为以前的dex2oat都是将所有的类在apk预优化/安装时就全部编译(speed),这也是以前odex占用空间大、安装时间长的原因;但是从(Android N)开始,Android开始引入了Profile来记录hot class、hotmethod,也就是只针对热点代码来做编译优化
AppImage:也就是我们常见到的XXX.art文件,这个文件里预加载了一些class,在应用运行时可以直接将AppImage映射到内存中,以加快类加载的速度.(Android O已经使用)

speed-profile:其效果(带有profile)对比默认的verify/quicken带来的效果是质的飞跃,从启动速度单项来说,可以提升启动速度10%-30%,简直起飞
AppImage:art预加载技术的出现又带来了一次飞跃,预计性能提升5%-10%

ps: 针对这类技术的思考,其实后面衍生了很多新的技术,如预加载,这个不仅仅可以用于art,可以用于很多很多方面。预加载这块目前各大厂商都有布局

3. Android S相对于Android R的变化

变化

去掉了quicken、用verify替代。Android S verify实际效果会比Android R的quicken还要好
speed-profile如果不带profile安装,会切换成verify模式。Android S verify实际效果和Android R speed-profile如果不带profile安装差异不大
单个app:平均安装时间Android S略大于Android R(5左右),Android S和Android R安装后大小几乎一致(S可能稍小),启动速度方面S略弱于R(3%左右)

性能

Android R:Verify < quicken(提升5%-8%) < speed-profile不带profile的情况(5%-10%) < speed < speed-profile带有profile的

Android S:Verify = speed-profile不带profile的情况 < speed < speed-profile带有profile的
Ps: Android S Verify = speed-profile不带profile的情况(几乎是一样的,也就是说S上我们没有必要对compiler-filter进行特殊配置)
Ps: Android S(speed-profile不带profile:需要修改art代码才会出来)

一般情况下speed-profile会比speed效能好(这里指的是启动速度),这个是由于启动app的时候会导入dex到内存,如果编译内容越大,导入速度越长,
这个带来的损耗如果不能从编译机器码带来性能提升上来弥补的话,甚至存在性能下降的情况。

所占用的rom空间

Android S:Verify < speed-profile带有profile的 < speed
可以看到speed的占用最大,预计是普通的Verify的2倍左右;speed-profile居中,预计是普通的Verify的1.5倍左右

下面是Android S的开源代码(Android S还有一个很大的改动,就是目前art全部都是gms 里面的art(除了Android go的版本),都给mainline了)

//art/libartbase/base/compiler_filter.cc
bool CompilerFilter::ParseCompilerFilter(const char* option, Filter* filter) {
  CHECK(filter != nullptr);

  if (strcmp(option, "verify-none") == 0) {
    LOG(WARNING) << "'verify-none' is an obsolete compiler filter name that will be "
                 << "removed in future releases, please use 'assume-verified' instead.";
    *filter = kAssumeVerified;
  } else if (strcmp(option, "interpret-only") == 0) {
    LOG(WARNING) << "'interpret-only' is an obsolete compiler filter name that will be "
                 << "removed in future releases, please use 'quicken' instead.";
    *filter = kVerify;
  } else if (strcmp(option, "verify-profile") == 0) {
    LOG(WARNING) << "'verify-profile' is an obsolete compiler filter name that will be "
                 << "removed in future releases, please use 'verify' instead.";
    *filter = kVerify;
  } else if (strcmp(option, "verify-at-runtime") == 0) {
    LOG(WARNING) << "'verify-at-runtime' is an obsolete compiler filter name that will be "
                 << "removed in future releases, please use 'extract' instead.";
    *filter = kExtract;
  } else if (strcmp(option, "balanced") == 0) {
    LOG(WARNING) << "'balanced' is an obsolete compiler filter name that will be "
                 << "removed in future releases, please use 'speed' instead.";
    *filter = kSpeed;
  } else if (strcmp(option, "time") == 0) {
    LOG(WARNING) << "'time' is an obsolete compiler filter name that will be "
                 << "removed in future releases, please use 'space' instead.";
    *filter = kSpace;
  } else if (strcmp(option, "assume-verified") == 0) {
    *filter = kAssumeVerified;
  } else if (strcmp(option, "extract") == 0) {
    *filter = kExtract;
  } else if (strcmp(option, "verify") == 0) {
    *filter = kVerify;
  } else if (strcmp(option, "quicken") == 0) {
    // b/170086509 'quicken' becomes an alias to 'verify.
    *filter = kVerify;
  } else if (strcmp(option, "space") == 0) {
    *filter = kSpace;
  } else if (strcmp(option, "space-profile") == 0) {
    *filter = kSpaceProfile;
  } else if (strcmp(option, "speed") == 0) {
    *filter = kSpeed;
  } else if (strcmp(option, "speed-profile") == 0) {
    *filter = kSpeedProfile;
  } else if (strcmp(option, "everything") == 0) {
    *filter = kEverything;
  } else if (strcmp(option, "everything-profile") == 0) {
    *filter = kEverythingProfile;
  } else {
    return false;
  }
  return true;
}
//art/libartbase/base/compiler_filter.h
  enum Filter {
    kAssumeVerified,      // Skip verification but mark all classes as verified anyway.
    kExtract,             // Delay verication to runtime, do not compile anything.
    kVerify,              // Only verify classes.
    kSpaceProfile,        // Maximize space savings based on profile.
    kSpace,               // Maximize space savings.
    kSpeedProfile,        // Maximize runtime performance based on profile.
    kSpeed,               // Maximize runtime performance.
    kEverythingProfile,   // Compile everything capable of being compiled based on profile.
    kEverything,          // Compile everything capable of being compiled.
  };