【问题标题】:Why does test1() run much faster than test2()?为什么 test1() 比 test2() 运行得快得多?
【发布时间】:2014-06-21 16:11:06
【问题描述】:
import java.util.Random;


public class Test{
    static int r = new Random().nextInt(2);
    static int a(){
        return r==1 ? 1 :0;
    }

    public static void test1() throws  Exception {
        //
        System.out.println(1403187139018L);
        for (int i = 0; i <   1073741824; i++) {}//*

        // Thread.sleep(20000);
        long d = 0;

        for (int j = 0; j < 10; j++) {
            long y = System.currentTimeMillis();

            for (int x = 0; x < 1073741823; x++) {
                d += r==0?1:0;
            }
            System.out.println((System.currentTimeMillis() -y));
        }
    }

    public static void test2()  throws  Exception{

        // Thread.sleep(20000);
        long d = 0;

        for (int j = 0; j < 10; j++) {
            long y = System.currentTimeMillis();

            for (int x = 0; x < 1073741824; x++) {
                d += r==0?1:0;
            }
            System.out.println((System.currentTimeMillis() -y));

            // System.out.println("time:"+ (System.currentTimeMillis() - y));
        }
    }

    public static void main(String[] args) throws  Exception{
        // Thread.sleep(20000);

        test1();
        test2();

    }

}

当我运行上面的代码时,我得到这个输出:

32
26
28
28
32
29
35
33
30
31
1321
1308
1324
1277
1348
1321
1337
1413
1287
1331

为什么 test1 快得多?

除了以下几点没有区别:

System.out.println(1403187139018L);
for (int i = 0; i <   1073741824; i++) {}//*

另外,test1 的时间成本是 25-35 毫秒,我觉得难以置信。我用 C 语言编写了相同的代码,运行每个 for 循环大约需要 4 秒。

这种行为看起来很奇怪。我怎么知道何时添加:

System.out.println(1403187139018L);
for (int i = 0; i <   1073741824; i++) {}//*

另外,如果我改变了

r==0?1:0

a()

那么 test2() 比 test1() 运行得快。

我得到的输出是:

1403187139018
3726
3729
3619
3602
3797
4362
4498
3816
4143
4368
1673
1386
1388
1323
1296
1337
1294
1283
1235
1460

原始遗留代码: ...

long t = System.currentTimeMillis();
MappedByteBuffer mbb = map(new File("temp.mmp"), 1024L * 1024 * 1024);

System.out.println("load " + (System.currentTimeMillis() - t));//*
for (int i = 0; i < 2014L * 1024 * 1024; i++) {}//*
int d = 0;
for (int j = 0; j < 10; j++) {
    t = System.currentTimeMillis();
    mbb.position(0);
    mbb.limit(mbb.capacity());

    for (int i = 0; i < mbb.capacity(); i++) {
        d += mbb.get();
    }

    ....
}
System.out.println(d);

【问题讨论】:

  • 这就是你检查生成代码的地方。
  • 不,你看JVM生成的代码。

标签: java performance profile


【解决方案1】:

空循环可能会在第一种方法中触发 JIT 编译。 JIT 足够聪明,可以意识到您的代码除了获取当前时间和打印时间差之外没有做任何有用的事情。所以它通过根本不运行它来优化无用的代码。

如果您编写实际有用的代码,JIT 会做正确的事。不要试图通过添加空循环来弄乱它。

【讨论】:

  • 这个。例如,尝试在循环后打印出值 od d。它仍然不正确,但时间会改变。
  • 是的,我添加了 System.out.println(d);在嵌套循环之后,结果没有改变。JIT 如何运行 for (int x = 0; x
  • 如果我添加代码 System.out.println(1403187139018L); 为什么 JIT 可以在第一个示例中工作for (int i = 0; i
  • JIT 检测经常运行的代码(比如你的大循环什么都不做),并尝试优化它。这样做很聪明。所以它检测到另一个循环除了修改在代码中任何地方都没有使用的局部变量之外什么都不做。所以它检测到这段代码唯一要做的就是白白消耗 CPU 周期。所以它删除了这段代码。
【解决方案2】:

影响JIT编译的因素太多了:

  1. 执行统计。当解释器运行一个方法时,它会收集不同的统计信息:执行了哪些路径,采用了哪些分支,看到了哪些类实例等。在test1 中,统计信息是在第一个(空)循环内收集的,因此会欺骗 JIT 编译器真实的执行场景。
  2. 类初始化和不常见的陷阱。当您从test1 中删除第一个System.out.println 时,并非所有与打印相关的类都被初始化。尝试调用未初始化类的方法会导致不常见的陷阱,从而导致去优化并使用新知识进一步重新编译该方法。
  3. 当您将r==0?1:0 替换为a() 时,test1 中收集的错误统计信息会成为一个坏笑话。在编译的test1 方法a() 之前从未执行过,因此没有机会进行优化。这就是为什么它比test2 运行得慢的原因,它是用a() 的知识编译的。

当然,在尝试从头开始编写微基准测试时,很难预测影响 JIT 编译的所有因素。这就是为什么推荐的对代码进行基准测试的方法是使用已经解决了大部分问题的特殊框架。我个人建议JMH

【讨论】:

    【解决方案3】:

    当代码被优化时,它使用它所拥有的关于代码之前如何运行的信息来优化它。在 test1() 中,第一个循环触发整个方法进行优化,但是没有关于如何运行第二个循环的信息,因此它没有像 test2() 那样优化

    我期望发生的事情是该方法被重新优化,但是代码必须检测到它第一次做出的假设是无效的。

    猜测可能有什么不同,test2() 可能已经展开循环,而在 test1() 中它没有。这可以解释性能上的差异。

    【讨论】:

    • 当我更改 r==0 时,我以为原因是 JIT 或 cpu 调度程序 .BUt ? 1:0 到 a(),输出完全改变。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-21
    • 2015-02-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-06-28
    相关资源
    最近更新 更多