我可以期望Dalvik和Android工具链有哪些优化?


67

我正在开发一个高性能的Android应用程序(游戏),尽管我首先尝试编写代码以提高可读性,但我还是想在脑海中默默地了解引擎盖下正在发生的事情。使用C ++,我对编译器将为我做什么和不为我做什么有了很好的直觉。我正在尝试对Java / Android执行相同的操作。

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

  • 方法内联。在什么条件下?private始终可以安全地内联方法;会完成吗?public final方法怎么样?其他类的对象上的方法?static方法?如果编译器可以轻松推断出对象的运行时类型怎么办?我应该声明方法final还是static尽可能声明方法?

  • 常见子表达式消除。例如,如果我访问someObject.someField两次,查找将仅执行一次吗?如果是给吸气剂打电话怎么办?如果我两次使用算术表达式怎么办?只会评估一次吗?如果我使用某个表达式的结果作为for循环的上限,该表达式的值我不知道会改变怎么办?

  • 边界检查数组查找。工具链会在某些情况下(例如原型for循环)消除这种情况吗?

  • 价值内联。是否public static final int总是会内联访问某些内容?即使他们在另一个班上?即使他们在另一个包裹中?

  • 分支预测。这甚至有多大的问题?在典型的Android设备上分支性能是否受到重大影响?

  • 简单的算术。会someInt * 2被取代someInt << 1吗?

Etcetera ...




@pablochan:有人在我的枕头下睡了一段时间了,但谢谢:) @Lior:很好的推荐信,这些对我来说是新的,谢谢!
Thomas

“ someInt * 2”和“ someInt << 2”不是完全在做同一件事;)我认为您的意思是“ someInt << 1”,我编辑了您的问题...
SyntaxT3rr0r 2011年

D'oh,谢谢:)(即使那样,它们在Java wrt溢出或负数中可能具有微妙的语义?Dunno,从未检查过……)
Thomas

Answers:


105

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

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

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

在Gingerbread中,我们为getter / setter方法添加了简单的内联。由于底层的JIT前端仍然基于简单的跟踪,因此如果被调用方在其中具有分支,则不会内联。但是实现了内联缓存机制,因此可以内联虚拟吸气剂/设置器而不会出现问题。

我们目前正在努力扩大编译范围,使其超出简单的跟踪范围,以便编译器具有更大的代码分析和优化窗口。敬请关注。


11
您注册专门回答这个问题?谢谢!太糟糕了,操作员推出姜饼的速度太慢了;我认为至少要一年才能指望这些优化。这些都是好东西,但是由于我不是编译器作家,所以我很难看到如何在实践中应用它。特别是:...
Thomas

2
(1)如果循环的上限取决于非最终变量(例如字段),是否还会执行循环优化,还是应该将上限存储在最终的局部变量中?(2)对于重复访问的所有字段都存在相同的问题。(3)我应该final尽可能声明我的方法吗?还是仍算作虚拟呼叫站点?(4)通常,内联不是在JIT级别完成的,但是也许您碰巧知道编译器和/或dx工具是否这样做?
托马斯

2
(1)只要上下限是循环不变的,声明局部变量并在循环外读取是最干净的方法。(2)JIT尚未进行循环不变优化。所以再次,您将要刻录局部变量以在循环外读取它。(3)是,请(4)我们要保持开发人员调试代码的能力,以便dx不执行内联。但是JIT很快就会积极地内联。

3
考虑到这个问题仍然有意义,您现在可以@Ben +1回顾一下Ant所做的优化。那将是真棒!
loretoparisi 2016年

10

我敢肯定,我的答案不会回答您所有的问题,但我想,即使回答一个问题也是一个胜利。

您似乎对该主题有很深的了解,知道您想要什么,所以您可能想要执行以下操作。构建一个包含您要研究的方面的示例应用程序。

拿取您获得的APK并通过APK工具运行它。众所周知,对您自己的代码进行反向工程以完全按照您的预期进行。

APK工具将提取并解码您的资源,并将工程.dex文件反向转换为.smali文件。您可能还希望查找smali项目,以获取有关如何读取.smali文件及其限制的更多信息。

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


1
好答案,谢谢。我尚未进行过这种调查,主要是因为这需要花费很多时间。这将至少表明Java编译器和dx正在做什么,尽管JITter的影响仍不确定。如果我确实有好奇心并且走这条路,我一定会把我的结果发回到这里。
Thomas

是的,请这样做。我本人对结果很感兴趣。
Octavian A. Damiean 2011年

1
javac做了一些优化,但是没有什么戏剧性的。“ dx”提供了其输入的忠实转换。正如Ben所指出的那样,如果这些事情不是真的,那么您将在调试器中苦苦挣扎。有关实践中的示例,请参阅groups.google.com/group/android-platform/browse_thread/thread/…(尤其是如果您不将“ -g”传递给javac的话,“ dx”会产生更好的代码) 。您还应该研究ProGuard的优化。
fadden

5

首先,请允许我说我不是达尔维克专家,我的一些回答可能是错误的。但是我已经在dalvik中研究了JIT代码,并且我对dalvik运行的字节码非常熟悉。

  1. 方法内联-据我所知,这永远不会发生。我几乎肯定,它永远不会在字节码级别上发生,而且我认为当前它不会在JIT级别上发生-尽管将来可能会发生。

  2. 常见的子表达式消除-我相信这只会对不使用任何非最终变量/字段的子表达式完成。如果那会发生,我并不完全肯定。如果完成了,我希望它可以在字节码级别(可能不是JIT级别)完成。

  3. 数组查找的边界检查-不知道

  4. 价值内联-据我所知,是的-它们将在所有这些情况下内联。

  5. 分支预测-不确定

  6. 简单的算术-据我所知

另外,我想提一下您使用的另一种方法-dx和dalvik都是开源的,因此您可以随意研究它们。尽管它们显然不是很小的代码库,所以在该级别上深入研究它们会花费很多精力。


好吧,如果需要解决的话,我在手工内联方法和缓存子表达式结果方面一直做得很好。谢谢!
托马斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.