【问题标题】:Optimization by Java CompilerJava编译器优化
【发布时间】:2011-08-24 07:10:54
【问题描述】:

最近,我在读这个article

根据那篇文章,Java 编译器(即 javac)在生成字节码时不会执行任何优化。这是真的吗?如果是这样,那么它是否可以作为中间代码生成器来实现以去除冗余并生成最优代码?

【问题讨论】:

  • 不完全是重复的,但答案可能很有趣:stackoverflow.com/questions/1680024/…
  • 嗯,那篇文章已经有将近十年的历史了。它仍然是真的(我不声称知道任何一种方式)?我建议您使用更现代的文档进行一些研究。

标签: java optimization javac


【解决方案1】:

编译器不优化字节码,因为它在运行时由 JIT 优化器优化。

如果您所针对的运行时类型没有 JIT 优化器(即使它有 JIT 编译器),或者您正在 AOT 编译,我建议使用优化混淆器,如 Proguard 或 Allatori。

【讨论】:

    【解决方案2】:

    要优化您的字节码,您可以使用Proguard

    正如其他人所指出的,主流 JVM 中的 JIT 会在编译代码时对其进行优化。它可能会优于 Proguard,因为它可以访问更多上下文。但在更简单的虚拟机中可能并非如此。在 Android 世界中,针对 Dalvik(Lollipop 之前的 Android 附带的 VM)使用 Proguard 优化是一种常见的做法。

    Proguard 还会缩小和混淆字节码,这在交付客户端应用程序时是必须的(即使您不使用优化)。

    【讨论】:

      【解决方案3】:

      javac 编译器曾经支持通过在命令行上传递 -o 来生成优化字节码的选项。

      然而,从 J2SE1.3 开始,the HotSpot JVM was shipped with the platform 引入了动态技术,例如即时编译和常见执行路径的自适应优化。因此,Java 编译器忽略了-o,从这个版本开始。

      我在阅读 Ant javac 任务及其 optimize 属性时遇到了这个标志:

      指示是否应优化编译源代码;默认为off注意,从 JDK 1.3 开始,Sun 的 javac 会忽略此标志(因为不需要编译时优化)。

      this page 中提到了 HotSpot JVM 的动态优化相对于编译时优化的优势:

      服务器 VM 包含一个高级自适应编译器,它支持许多由优化 C++ 编译器执行的优化类型,以及一些传统编译器无法完成的优化,例如跨虚拟方法调用的激进内联。这是相对于静态编译器的竞争和性能优势。自适应优化技术的方法非常灵活,通常甚至优于高级静态分析和编译技术。

      【讨论】:

        【解决方案4】:

        我过去曾研究过输出的 Java 字节码(使用名为 FrontEnd 的应用程序)。它基本上不做任何优化,除了内联常量(静态final)和预先计算固定表达式(如2*5和“ab”+“cd”)。这就是为什么这么容易拆卸的部分原因(使用名为 JAD 的应用程序)

        我还发现了一些有趣的点来优化您的 java 代码。它帮助我将内循环的速度提高了 2.5 倍。

        一个方法有 5 个快速访问变量。当这些变量被调用时,它们比所有其他变量都快(可能是因为堆栈维护)。方法的参数也计入这 5 个。因此,如果您在 for 循环中的代码执行了一百万次,请在方法开始时分配这些变量,并且没有参数。

        局部变量也比字段快,因此如果您在内部循环中使用字段,请通过在方法开始时将它们分配给局部变量来缓存这些变量。缓存引用而不是内容。 (如:int[] px = this.pixels;)

        【讨论】:

        • 常量表达式处理(包括最终变量 - 如果它们具有常量值,则不必是静态变量)实际上由语言规范规定。
        • 你能提供更多关于这“5个快速访问变量”的信息吗?我知道aload_<n> 字节码指令,但其中只有4 个,从0 到3(astore_<n> 相同)。我什至不确定它们是否比普通的aload 操作执行得更好(除了它们不需要额外的索引字节,所以安全一些空间)。不过,我同意“局部变量与字段”的论点。
        【解决方案5】:

        当我读到这个部分时,我停止了阅读:

        更重要的是javac编译器 不执行简单的优化 像循环展开,代数 简化,强度降低, 和别的。为了获得这些好处和 其他简单的优化, 程序员必须在 Java源代码并没有依赖 javac 编译器来执行它们。

        首先,对 Java 源代码进行循环展开几乎不是一个好主意。 javac 没有在优化方面做太多的原因是它是由 JVM 中的 JIT 编译器完成的,它可以做出比编译器更好的决策,因为它可以准确地看到哪些代码运行得最多.

        【讨论】:

          【解决方案6】:

          javac 只会做很少的优化,如果有的话。

          关键是 JIT 编译器完成了大部分优化 - 如果它有很多信息,它的效果最好,如果javac 也执行优化,其中一些可能会丢失。如果javac 执行了某种循环展开,JIT 将更难以一般方式自己执行此操作 - 并且它有更多关于哪些优化将实际起作用的信息,因为它知道目标平台。

          【讨论】:

          • -1 用于传播 Java 神话,即编译时优化是不可取的。一些编译过程可能非常昂贵,传播 JIT 应该负责完成所有工作的想法是无稽之谈。事实上,在优化方面,C 和 C++ 仍然比 Java 做得更好。
          • @Waneck:这是您的意见,但显然不是 Java 实现团队的意见。 javac 确实 做了很少的优化(如果有的话)。是的,一些优化可能很昂贵,这就是 Hotspot 逐步进行优化的原因。而且它使用有关目标环境的更多信息(包括实际加载的类)的事实允许它执行一些无法静态执行的优化。在某些情况下,AOT 优化当然会更好,但反过来也是如此。
          • 我了解JIT编译可以进行编译时无法进行的优化;但是,在编译时不执行任何优化并不是借口,这就是我所指的 Java 神话。
          • @Waneck:那么您认为应该在编译时执行哪些确切优化?请记住,输出是基于每个类的,而不是整个二进制文件。 (这里在执行模型、部署单元等方面的差异非常显着。)
          • 根据我的经验,Java 中最有效的基于编译器的优化与泛型特化、内联和避免装箱操作有关。例如,这篇 scala 论文lampwww.epfl.ch/~dragos/files/scala-spec.pdf 表明,执行函数的专门化导致了 20 倍的加速,而数组的专门化导致了 40 倍。这是一个表明 JIT 不是圣杯的示例,并且 AOT 可以与 JIT 齐头并进。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-11-07
          • 1970-01-01
          • 1970-01-01
          • 2014-02-21
          相关资源
          最近更新 更多