Java 当JVM不执行代码优化的方法时
在阅读Scott Oaks的《Java性能》一书时,我遇到了一段代码,其中说Java 7或8 JVM足够聪明,可以跳过for循环中提供的计算部分,因为将来不会使用结果(微基准标记) 书中提到的代码:Java 当JVM不执行代码优化的方法时,java,jvm,Java,Jvm,在阅读Scott Oaks的《Java性能》一书时,我遇到了一段代码,其中说Java 7或8 JVM足够聪明,可以跳过for循环中提供的计算部分,因为将来不会使用结果(微基准标记) 书中提到的代码: public void doTest() { // Main Loop double l; long then = System.currentTimeMillis(); for (int i = 0; i < nLoops; i++) { l
public void doTest() {
// Main Loop
double l;
long then = System.currentTimeMillis();
for (int i = 0; i < nLoops; i++) {
l = fibImpl1(50);
}
long now = System.currentTimeMillis();
System.out.println("Elapsed time: " + (now - then));
}
private double fibImpl1(int n) {
if (n < 0) throw new IllegalArgumentException("Must be > 0");
if (n == 0) return 0d;
if (n == 1) return 1d;
double d = fibImpl1(n - 2) + fibImpl(n - 1);
if (Double.isInfinite(d)) throw new ArithmeticException("Overflow");
return d;
}
为了验证这一点,我尝试了一个代码,但运行时间计算并没有反映上述理论
我的代码版本:
public class APSum {
public static void main(String[] args) {
long then = System.currentTimeMillis();
ArithmeticProgression.sum(500000000);
long now = System.currentTimeMillis();
System.out.println("Elapsed time: " + (now - then));
}
}
class ArithmeticProgression{
public static double sum(int i){
double sum=0;
for(int index=0; index<=i; index++){
sum = sum + (double)index;
}
return sum;
}
}
public-class-APSum{
公共静态void main(字符串[]args){
long then=System.currentTimeMillis();
算术级数和(500000000);
long now=System.currentTimeMillis();
System.out.println(“经过的时间:+(现在-然后));
}
}
类算术级数{
公共静态双和(int i){
双和=0;
对于(int index=0;index现代JVM太复杂了,需要进行各种优化。如果您试图测量一些小代码,如果没有对JVM正在做的事情的非常详细的了解,要正确地执行它就非常复杂。死代码消除(DCE)是一种经常导致微基准出错的优化
有两个经典错误(阅读更多关于常见错误和错误的信息):
- 因为编译的最小单元是方法,所以基准测试必须有多个主方法
- 没有预热迭代。始终包括一个预热阶段,该阶段将一直运行测试内核,足以在计时阶段之前触发所有初始化和编译
修正后,我们的基准如下所示:
public class APSum {
public static void main(String[] args) {
for (int i = 0; i < 5000; i++) {
test();
}
}
private static void test() {
long then = System.currentTimeMillis();
sum(5000000);
long now = System.currentTimeMillis();
System.out.println("Elapsed time: " + (now - then));
}
public static double sum(int i){
double sum=0;
for(int index=0; index<=i; index++){
sum = sum + (double)index;
}
return sum;
}
}
编译器未能内联方法APSum::sum,原因是。事实上,虽然在基准测试中(尤其是在微基准测试中)经常触发,但在应用程序代码中触发的频率较低。为了获得正确的结果,我们必须添加更多预热迭代:
public class APSum {
public static void main(String[] args) {
for (int i = 0; i < 10000; i++) {
test();
}
}
private static void test() {
long then = System.currentTimeMillis();
sum(5000000);
long now = System.currentTimeMillis();
System.out.println("Elapsed time: " + (now - then));
}
public static double sum(int i){
double sum=0;
for(int index=0; index<=i; index++){
sum = sum + (double)index;
}
return sum;
}
}
你试过书中的代码了吗?编译器是聪明的,但它不是无限聪明的。因为没有使用结果,它可以扔掉一些东西的程度是有限的。你的例子可能太复杂了,所以编译器不会扔掉它。你在混淆概念。据我所知,java compi将源代码转换为字节码(类文件)的ler它非常愚蠢。与其他编译器(如gcc)相比,它没有做任何重要的优化。换句话说:编译器构造函数知道优化的许多概念中,只有很少的概念被应用。如果Java7、8改变了这一点,我一定错过了它……但发生的事情是,即时编译器不断地优化在运行时编写代码;请参见此处示例。此外,JIT优化高度依赖于平台。因此,不要期望同一代码在不同平台上的行为一致。您可能正在研究一种专门的JVM优化,因为执行优化的不是编译器,而是JIT。Javac和您的Java一样愚蠢编译器的get。由于OSR
,内联失败?为什么?内联不应该是对OSR的补充吗?@Eugene(来源):“如果我们使用C1编译一个分析方法,并且已知被调用方在C2版本中有OSRed,请不要内联。”
@ 0 java.lang.System::currentTimeMillis (0 bytes) intrinsic
@ 6 edu.jvm.runtime.APSum::sum (22 bytes) inlining prohibited by policy
@ 10 java.lang.System::currentTimeMillis (0 bytes) intrinsic
public class APSum {
public static void main(String[] args) {
for (int i = 0; i < 10000; i++) {
test();
}
}
private static void test() {
long then = System.currentTimeMillis();
sum(5000000);
long now = System.currentTimeMillis();
System.out.println("Elapsed time: " + (now - then));
}
public static double sum(int i){
double sum=0;
for(int index=0; index<=i; index++){
sum = sum + (double)index;
}
return sum;
}
}
Elapsed time: 5
Elapsed time: 4
Elapsed time: 5
@ 0 java.lang.System::currentTimeMillis (0 bytes) (intrinsic)
@ 6 edu.jvm.runtime.APSum::sum (22 bytes) inline (hot)
@ 10 java.lang.System::currentTimeMillis (0 bytes) (intrinsic)
Elapsed time: 0
Elapsed time: 0
Elapsed time: 0