为什么 Java 编译器不优化一个简单的方法?

Why is Java compiler not optimizing a trivial method?

我有一个简单的 class 用于说明目的:

public class Test {

    public int test1() {
        int result = 100;
        result = 200;
        return result;
    }

    public int test2() {
        return 200;
    }
}

编译器生成的字节码(由javap -c Test.class检查)如下:

public int test1();
Code:
   0: bipush        100
   2: istore_1
   3: sipush        200
   6: istore_1
   7: iload_1
   8: ireturn

public int test2();
Code:
   0: sipush        200
   3: ireturn

为什么编译器没有将 test1 方法优化为为 test2 方法生成的相同字节码?考虑到很容易得出根本未使用值 100 的结论,我希望它至少避免对 result 变量进行冗余初始化。

我在 Eclipse 编译器和 javac 中观察到了这一点。

javac 版本:1.8.0_72,与 Java:

一起作为 JDK 的一部分安装
Java(TM) SE Runtime Environment (build 1.8.0_72-b15)
Java HotSpot(TM) 64-Bit Server VM (build 25.72-b15, mixed mode)

JVM 优化字节码,创建称为 代码缓存 的东西。与 C++ 不同,JVM 可以收集有关您的程序的大量数据,例如,for 循环有多热?该代码块是否值得优化?, 等。所以在这里优化是非常有用的,并且通常会产生更好的结果。

如果您在从 java 转换为字节码时进行优化(即,当您调用 javac 时),您的代码可能最适合您的计算机,但不适用于某些不同的平台。所以在这里优化是没有意义的。

举个例子,假设您的程序使用 AES 加密。现代 CPUs 有特定于 AES 的指令集,使用特殊硬件使加密速度更快。

如果javac试图在编译时进行优化,那么它要么

  • 在软件级别优化指令,在这种情况下,您的程序不会受益于现代 CPUs,或者,
  • 用等效的 CPU-AES 指令替换您的 AES 指令,仅在新的 CPU 上受支持,这会降低您的兼容性。

如果 javac 让它们保持字节码中的原样,那么更新的 CPU 上的 JVM 运行 可以识别它们作为 AES 并利用此 CPU 功能,而旧 CPUs 上的 JVM 运行 可以在运行时(代码缓存)在软件级别优化它们,为您提供 优化 兼容性

典型的 Java 虚拟机会在运行时优化您的程序,而不是在编译期间。在运行时,JVM 对您的应用程序了解得更多,包括程序的实际行为和执行程序的实际硬件。

字节码只是对您的程序应该如何运行的描述。运行时可以自由地对您的字节代码应用任何优化。

当然,有人可能会争辩说,即使在编译期间也可以应用这种微不足道的优化,但通常不将优化分布在多个步骤中是有意义的。任何优化都会有效地导致有关原始程序的信息丢失,这可能会使其他优化变得不可能。也就是说,并非所有 "best optimizations" 总是显而易见的。一个简单的方法是在编译期间简单地放弃(几乎)所有优化,并在运行时应用它们。