我可以期待Dalvik和Android工具链的哪些优化?

2022-08-31 14:45:11

我正在开发一个高性能的Android应用程序(一个游戏),虽然我试图首先编写可读性,但我喜欢在我的脑海中保留一幅关于引擎盖下发生的事情的图片。通过C++,我已经对编译器会做什么和不会为我做什么有了相当好的直觉。我正在尝试为Java / Android做同样的事情。

因此,这个问题。我在网上找到的关于这个话题的信息很少。Java编译器,Dalvik转换器(dx)和/或JITter(在Android 2.2 +上)是否会执行如下优化?

  • 方法内联。在什么条件下? 方法始终可以安全地内联;会这样做吗?方法如何?其他类对象上的方法? 方法?如果编译器可以轻松推断出对象的运行时类型,该怎么办?我应该尽可能或尽可能声明方法吗?privatepublic finalstaticfinalstatic

  • 常见的子表达消除。例如,如果我访问两次,查找是否只执行一次?如果这是对一个获取者的呼叫呢?如果我使用一些算术表达式两次怎么办?它只会被评估一次吗?如果我使用某个表达式的结果(我知道其值不会改变)作为循环的上限,该怎么办?someObject.someFieldfor

  • 在数组查找时进行边界检查。工具链会在某些条件下消除这种情况吗,比如原型循环?for

  • 值内联。对某些内容的访问是否始终是内联的?即使他们在另一个班级?即使它们位于另一个包中?public static final int

  • 分支预测。这到底是一个多大的问题?分支是否对典型的Android设备有很大的性能影响?

  • 简单的算术。将被 替换为 ?someInt * 2someInt << 1

诸如此类。。。


答案 1

这是Ben,JIT @ Google的工程师之一。当Bill和我开始这个项目时,目标是尽快交付一个有效的JIT,对资源争用的影响最小(例如内存占用,CPU被编译器线程劫持),以便它也可以在低端设备上运行。因此,我们使用了一个非常原始的基于跟踪的模型。也就是说,传递给 JIT 编译器的编译实体是一个基本块,有时短如单个指令。此类跟踪将在运行时通过称为链接的技术拼接在一起,这样解释器和代码缓存查找就不会经常被调用。在某种程度上,加速的主要来源来自消除频繁执行的代码路径上的重复解释器解析开销。

也就是说,我们确实使用Froyo JIT实现了相当多的本地优化:

  • 寄存器分配(v5te 目标有 8 个寄存器,因为 JIT 会生成 Thumb 代码/v7 需要 16 个寄存器)
  • 调度(例如,Dalvik寄存器的冗余ld /st消除,负载提升,存储下沉)
  • 冗余空检查消除(如果在基本块中可以找到此类冗余)。
  • 简单计数循环的循环形成和优化(即循环体中没有侧出口)。对于此类循环,将优化基于扩展归纳变量的数组访问,以便仅在循环序幕中执行 null 和范围检查。
  • 每个虚拟调用站点一个条目内联缓存,运行时具有动态修补功能。
  • 窥视孔优化,例如 mul/div 的文字操作数的功率降低。

在姜饼中,我们为 getters/setters 添加了简单的内联。由于底层 JIT 前端仍然是基于简单跟踪的,如果被调用方在其中有分支,则不会内联它。但是实现了内联缓存机制,以便可以毫无问题地内联虚拟 getter/setter。

我们目前正致力于将编译范围扩大到简单的跟踪之外,以便编译器具有更大的代码分析和优化窗口。敬请期待。


答案 2

我相信我的答案不会回答你所有的问题,但我想如果它回答了哪怕一个,那也是一场胜利。

您似乎对该主题有深刻的了解,并且知道自己想要什么,因此您可能希望执行以下操作。生成一个示例应用程序,其中包含要调查的方面。

获取您获得的APK,然后通过APK工具运行它。正如我们所知,对您自己的代码进行逆向工程以执行您打算做的事情是完全可以的。

APK工具将提取和解码您的资源,并将对文件进行逆向工程。您可能还想查找 smali 项目,以获取有关如何读取文件及其限制的更多信息。.dex.smali.smali

再一次,我很确定这不会回答你所有的问题,但这可能是一个良好的开端。


推荐