为什么 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" 总是显而易见的。一个简单的方法是在编译期间简单地放弃(几乎)所有优化,并在运行时应用它们。
我有一个简单的 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:
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" 总是显而易见的。一个简单的方法是在编译期间简单地放弃(几乎)所有优化,并在运行时应用它们。