为什么40亿次迭代Java循环只需要2 ms?

问题

我在配备2.7 GHz Intel Core i7的笔记本电脑上运行以下Java代码。我打算让它测量完成2 ^ 32次迭代循环所需的时间,我预计大约需要1.48秒(4 / 2.7 = 1.48)。

但实际上它只需要2毫秒,而不是1.48秒。我想知道这是否是下面任何JVM优化的结果?

public static void main(String[] args)
{
    long start = System.nanoTime();

    for (int i = Integer.MIN_VALUE; i < Integer.MAX_VALUE; i++){
    }
    long finish = System.nanoTime();
    long d = (finish - start) / 1000000;

    System.out.println("Used " + d);
}

#1 热门回答(105 赞)

这里有两种可能性之一:

  • 编译器意识到循环是冗余的,什么也不做,所以它优化了它。
  • JIT(即时编译器)意识到循环是冗余的,什么都不做,所以它优化了它。

现代编译器非常聪明;他们可以看到代码何时无用。尝试将空循环放入GodBolt并查看输出,然后打开-O2优化,你将看到输出是沿着

main():
    xor eax, eax
    ret

我想澄清一下,在Java中,大多数优化都是由JIT完成的。在其他一些语言(如C / C)中,大多数优化都是由第一个编译器完成的。


#2 热门回答(54 赞)

看起来它被JIT编译器优化了。当我关闭它(-Djava.compiler=NONE)时,代码运行得慢得多:

$ javac MyClass.java
$ java MyClass
Used 4
$ java -Djava.compiler=NONE MyClass
Used 40409

我把OP的代码放在class MyClass里面。


#3 热门回答(20 赞)

我只是陈述了显而易见的事实 - 这是一个JVM优化发生,循环将完全删除。这是一个小测试,显示a巨大的differenceJIT在启用/启用时仅用于279147330并且完全禁用。

免责声明:不要写这样的测试 - 这只是为了证明实际循环"删除"发生在C2 Compiler

@Benchmark
@Fork(1)
public void full() {
    long result = 0;
    for (int i = Integer.MIN_VALUE; i < Integer.MAX_VALUE; i++) {
        ++result;
    }
}

@Benchmark
@Fork(1)
public void minusOne() {
    long result = 0;
    for (int i = Integer.MIN_VALUE; i < Integer.MAX_VALUE - 1; i++) {
        ++result;
    }
}

@Benchmark
@Fork(value = 1, jvmArgsAppend = { "-XX:TieredStopAtLevel=1" })
public void withoutC2() {
    long result = 0;
    for (int i = Integer.MIN_VALUE; i < Integer.MAX_VALUE - 1; i++) {
        ++result;
    }
}

@Benchmark
@Fork(value = 1, jvmArgsAppend = { "-Xint" })
public void withoutAll() {
    long result = 0;
    for (int i = Integer.MIN_VALUE; i < Integer.MAX_VALUE - 1; i++) {
        ++result;
    }
}

结果显示,取决于启用JIT的哪一部分,方法变得更快(速度快得多,看起来它正在"无所事事" - 循环移除,这似乎发生在C2 Compiler-这是最高级别):

Benchmark                Mode  Cnt      Score   Error  Units
 Loop.full        avgt    2     ≈ 10⁻⁷          ms/op
 Loop.minusOne    avgt    2     ≈ 10⁻⁶          ms/op
 Loop.withoutAll  avgt    2  51782.751          ms/op
 Loop.withoutC2   avgt    2   1699.137          ms/op