我可以从 Dalvik 和 Android 工具链中获得哪些优化?
Posted
技术标签:
【中文标题】我可以从 Dalvik 和 Android 工具链中获得哪些优化?【英文标题】:What optimizations can I expect from Dalvik and the Android toolchain? 【发布时间】:2011-06-22 05:05:52 【问题描述】:我正在开发一个高性能的 android 应用程序(一个游戏),尽管我首先尝试编写代码以提高可读性,但我喜欢在脑海中保留一个幕后发生的事情的画面。使用 C++,我对编译器将为我做什么和不做什么有了相当好的直觉。我正在尝试为 Java/Android 做同样的事情。
因此提出了这个问题。我在网上几乎找不到关于这个话题的信息。 Java 编译器、Dalvik 转换器 (dx) 和/或 JITter(在 Android 2.2+ 上)会执行如下优化吗?
方法内联。在什么条件下? private
方法总是可以安全地内联;这会完成吗? public final
方法怎么样?其他类对象的方法? static
方法?如果编译器可以轻松推断出对象的运行时类型怎么办?我应该尽可能将方法声明为final
或static
吗?
通用子表达式消除。例如,如果我访问someObject.someField
两次,是否只进行一次查找?如果是对 getter 的调用呢?如果我两次使用某个算术表达式怎么办?它只会被评估一次吗?如果我使用某个表达式的结果(我知道它的值不会改变)作为for
循环的上限呢?
对数组查找进行边界检查。工具链会在某些情况下消除这种情况,例如典型的 for
循环吗?
值内联。对某些public static final int
的访问是否总是被内联?即使他们在另一个班级?即使它们在另一个包中?
分支预测。这是一个多大的问题?分支对典型 Android 设备的性能影响很大吗?
简单的算术。 someInt * 2
会被someInt << 1
取代吗?
等等……
【问题讨论】:
这可能有用:developer.android.com/guide/practices/design/performance.html 这些也可能有用:netmite.com/android/mydroid/dalvik/docs/dexopt.html、taranfx.com/android-internals-jit-froyo,以及演示文稿的 pdf 版本:android-app-developer.co.uk/android-app-development-docs/… @pablochan:那个在我枕头下已经有一段时间了,但是谢谢 :) @Lior:很好的参考资料,这些对我来说是新的,谢谢! "someInt * 2" 和 "someInt D'oh,谢谢 :) (即便如此,它们在 Java 中的语义可能略有不同 w.r.t. 溢出或负数?不知道,从未检查过......) 【参考方案1】:我是 Ben,他是负责 JIT @ Google 的工程师之一。当 Bill 和我开始这个项目时,目标是尽快交付一个有效的 JIT,同时尽量减少对资源争用的影响(例如内存占用、CPU 被编译器线程劫持),以便它可以在低端设备上运行好吧。因此,我们使用了一个非常原始的基于轨迹的模型。也就是说,传递给 JIT 编译器的编译实体是一个基本块,有时短到一条指令。这样的跟踪将在运行时通过一种称为链接的技术拼接在一起,这样解释器和代码缓存查找就不会经常被调用。在某种程度上,加速的主要来源是消除了频繁执行的代码路径上重复的解释器解析开销。
也就是说,我们确实使用 Froyo JIT 实现了很多本地优化:
寄存器分配(8 个寄存器用于 v5te 目标,因为 JIT 生成 Thumb 代码/16 个寄存器用于 v7) 调度(例如,Dalvik 寄存器的冗余 ld/st 消除、负载提升、存储下沉) 冗余空检查消除(如果可以在基本块中找到这种冗余)。 简单计数循环的循环形成和优化(即循环体中没有侧出口)。对于这样的循环,优化了基于扩展归纳变量的数组访问,以便仅在循环序言中执行空值和范围检查。 每个虚拟调用站点一个条目内联缓存,并在运行时进行动态修补。 窥孔优化,例如 mul/div 的文字操作数的功耗降低。在 Gingerbread 中,我们为 getter/setter 添加了简单的内联。由于底层的 JIT 前端仍然是简单的基于跟踪的,如果被调用者在那里有分支,它将不会被内联。但是实现了内联缓存机制,因此可以毫无问题地内联虚拟 getter/setter。
我们目前正在努力将编译范围扩大到超越简单跟踪的范围,以便编译器具有更大的代码分析和优化窗口。敬请期待。
【讨论】:
您是专门注册来回答这个问题的?谢谢!太糟糕了,运营商推出姜饼的速度如此之慢;我认为至少再过一年就不能指望这些优化了。这都是好东西,但由于我不是编译器编写者,我很难看到如何在实践中应用它。特别是:... (1) 如果循环的上限取决于非最终变量(例如字段),是否也进行循环优化,还是应该将上限存储在最终局部变量中? (2) 重复访问的所有字段的相同问题。 (3) 我是否应该尽可能声明我的方法final
?或者这仍然算作虚拟呼叫站点吗? (4) 内联一般不会在 JIT 级别完成,但也许你碰巧知道编译器和/或 dx 工具是否这样做?
(1) 只要上/下界是循环不变的,声明一个局部变量并在循环外读取它是最干净的方法。 (2) JIT 还没有进行循环不变优化。因此,您将再次需要刻录一个局部变量以在循环外读取它。 (3) 是的,请 (4) 我们希望保持开发人员调试代码的能力,因此 dx 不会执行内联。但很快 JIT 将积极内联。
@Ben +1 你能回顾一下 Ant 所做的优化吗,考虑到这个问题仍然有意义。那太棒了!【参考方案2】:
首先,让我先说我不是 dalvik 方面的专家,我的一些回答可能是错误的。但是我挖过dalvik中的JIT代码,对dalvik运行的字节码还是比较熟悉的。
方法内联 - 据我所知,这从未发生过。我几乎可以肯定它永远不会在字节码级别发生,而且我认为目前不会在 JIT 级别发生 - 尽管将来可能会发生。
通用子表达式消除 - 我相信这只适用于不使用任何非最终变量/字段的子表达式。即使到那时它也会发生,我并不完全肯定。如果完成,我希望它在字节码级别完成,可能不是 JIT 级别。
数组查找的边界检查 - 没有线索
值内联 - 据我所知,是的 - 它们将在所有这些场景中内联。
分支预测 - 不确定
简单的算术 - 据我所知没有
另外,我想向您提及另一种方法 - dx 和 dalvik 都是开源的,因此您可以随心所欲地挖掘它们。虽然,它们显然不是小的代码库,所以在那个级别上挖掘它们需要相当大的努力
【讨论】:
好吧,如果这是可以通过的,我在手动内联我的方法和缓存子表达式结果方面做得很好。谢谢!【参考方案3】:我确信我的回答不会回答你所有的问题,但我想如果它回答了一个问题,那就是一场胜利。
您似乎对该主题有深刻的了解并且知道自己想要什么,因此您可能想要执行以下操作。构建一个包含您要调查的方面的示例应用程序。
获取您获得的 APK 并通过 APK Tool 运行它。正如我们所知,对您自己的代码进行逆向工程以完成您想要的工作是非常好的。
APK 工具将提取和解码您的资源,并将 .dex
文件反向工程为 .smali
文件。您可能还想查看smali 项目以获取有关如何读取.smali
文件及其限制的更多信息。
再次,我很确定这不会回答您的所有问题,但它可能是一个好的开始。
【讨论】:
很好的答案,谢谢。我还没有想到那条调查线,主要是因为这需要很多时间。这将至少显示 Java 编译器和 dx 正在做什么,尽管 JITter 的影响仍然不确定。如果我确实感到好奇并沿着这条路走下去,我一定会在这里发布我的结果。 是的,请这样做。我自己对结果很感兴趣。 javac 做了一些优化,但没有什么戏剧性的。 "dx" 提供了对其输入的忠实转换。正如 Ben 指出的那样,如果这些事情不是真的,那么您会在调试器方面遇到很多困难。有关实践中的示例,请参阅groups.google.com/group/android-platform/browse_thread/thread/…(尤其是如果您不将“-g”传递给 javac,“dx”会产生更好的代码的部分)。您还应该研究 ProGuard 的优化。以上是关于我可以从 Dalvik 和 Android 工具链中获得哪些优化?的主要内容,如果未能解决你的问题,请参考以下文章