我正在开发一个高性能 Android 应用程序(一款游戏),虽然我首先尝试编写代码以提高可读性,但我喜欢在脑海中保留一幅幕后发生的事情的图片。通过 C++,我对编译器能为我做什么和不能做什么有了相当好的直觉。我正在尝试为 Java/Android 做同样的事情。
于是就有了这个问题。我在网上找不到关于这个主题的信息。 Java 编译器、Dalvik 转换器 (dx) 和/或 JITter(在 Android 2.2+ 上)是否会执行如下优化?
方法内联。在什么条件下?private
方法始终可以安全地内联;这会完成吗?怎么样public final
方法?其他类的对象上的方法?static
方法?如果编译器可以轻松推断出对象的运行时类型怎么办?我应该将方法声明为final
or static
尽可能?
公共子表达式消除。例如,如果我访问someObject.someField
两次,查找只进行一次吗?如果这是对 getter 的调用怎么办?如果我使用某个算术表达式两次怎么办?只会评估一次吗?如果我使用某个表达式的结果(我知道其值不会改变)作为 a 的上限会怎样?for
loop?
对数组查找进行边界检查。工具链是否会在某些情况下消除这种情况,例如典型的情况for
loop?
值内联。将访问一些public static final int
总是内联?即使他们在另一个班级?即使它们在另一个包裹中?
分支预测。这究竟是一个多大的问题呢?在典型的 Android 设备上,分支是否会对性能造成很大影响?
简单的算术。将要someInt * 2
被替换为someInt << 1
?
诸如此类...
我是 Ben,JIT @ Google 的工程师之一。当 Bill 和我开始这个项目时,目标是尽快交付一个有效的 JIT,同时对资源争用(例如内存占用、CPU 被编译器线程劫持)影响最小,以便它可以在低端设备上运行出色地。因此我们使用了一个非常原始的基于跟踪的模型。也就是说,传递给 JIT 编译器的编译实体是一个基本块,有时短至一条指令。此类跟踪将在运行时通过称为链接的技术缝合在一起,以便解释器和代码缓存查找不会经常被调用。在某种程度上,加速的主要来源来自于消除频繁执行的代码路径上重复的解释器解析开销。
也就是说,我们确实使用 Froyo JIT 实现了相当多的本地优化:
- 寄存器分配(由于 JIT 生成 Thumb 代码,v5te 目标有 8 个寄存器/v7 有 16 个寄存器)
- 调度(例如,Dalvik 寄存器的冗余 ld/st 消除、负载提升、存储下沉)
- 冗余空检查消除(如果可以在基本块中找到这种冗余)。
- 简单计数循环的循环形成和优化(即循环体中没有侧出口)。对于此类循环,基于扩展归纳变量的数组访问被优化,以便仅在循环序言中执行空和范围检查。
- 每个虚拟调用点有一个条目内联缓存,并在运行时进行动态修补。
- 窥视孔优化,例如 mul/div 字面操作数的功耗降低。
在 Gingerbread 中,我们为 getter/setter 添加了简单的内联。由于底层 JIT 前端仍然是基于简单的跟踪,因此如果被调用者在其中有分支,则不会内联。但是内联缓存机制的实现使得虚拟 getter/setter 可以毫无问题地内联。
我们目前正在努力扩大编译范围,超越简单的跟踪,以便编译器有更大的窗口进行代码分析和优化。敬请关注。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)