Java如何使低效的代码运行得比高效的代码更快?

时间:2012-05-17 23:39:09

标签: java performance optimization jvm jit

在以下代码段中,Foo1是一个每次调用方法bar()时递增计数器的类。 Foo2做同样的事情,但有一个额外的间接层。

我希望Foo1Foo2更快,但在实践中,Foo2始终比Foo1快40%。 JVM如何优化代码,使Foo2的运行速度超过Foo1

一些细节

  • 测试以java -server CompositionTest执行。
  • 使用java -client CompositionTest运行测试会产生预期结果,Foo2慢于Foo1
  • 切换循环的顺序没有区别。
  • 在sun和openjdk的JVM上使用java6验证了结果。

守则

public class CompositionTest {

    private static interface DoesBar {
        public void bar();
        public int count();
        public void count(int c);
    }

    private static final class Foo1 implements DoesBar {
        private int count = 0;
        public final void bar() { ++count; }
        public int count() { return count; }
        public void count(int c) { count = c; }
    }

    private static final class Foo2 implements DoesBar {
        private DoesBar bar;
        public Foo2(DoesBar bar) { this.bar = bar; }
        public final void bar() { bar.bar(); }
        public int count() { return bar.count(); }
        public void count(int c) { bar.count(c); }
    }

    public static void main(String[] args) {
        long time = 0;
        DoesBar bar = null;
        int reps = 100000000;

        for (int loop = 0; loop < 10; loop++) {
            bar = new Foo1();
            bar.count(0);

            int i = reps;
            time = System.nanoTime();
            while (i-- > 0) bar.bar();
            time = System.nanoTime() - time;

            if (reps != bar.count())
                throw new Error("reps != bar.count()");
        }
        System.out.println("Foo1 time: " + time);

        for (int loop = 0; loop < 10; loop++) {
            bar = new Foo2(new Foo1());
            bar.count(0);

            int i = reps;
            time = System.nanoTime();
            while (i-- > 0) bar.bar();
            time = System.nanoTime() - time;

            if (reps != bar.count())
                throw new Error("reps != bar.count()");
        }
        System.out.println("Foo2 time: " + time);
    }
}

2 个答案:

答案 0 :(得分:2)

你的microbench标记毫无意义。在我的计算机上,每个循环的代码运行大约8ms ...要获得任何有意义的数字,基准测试应该至少运行一秒钟。

当两者都运行大约一秒钟(提示,你需要超过Integer.MAX_VALUE次重复)时,我发现两者的运行时间是相同的。

可能的解释是,JIT编译器注意到你的间接是没有意义的并且优化了它(或者至少内联了方法调用),这样两个循环中执行的代码是相同的。

它可以这样做,因为它知道bar中的Foo2实际上是最终的,它也知道Foo2构造函数的参数始终是Foo1 (至少在我们的小测试中)。这样,它就知道调用Foo2.bar时的确切代码路径。它也知道这个循环会运行很多次(实际上它确切地知道循环执行的次数) - 所以内联代码似乎是一个好主意。

我不知道这是否正是它的作用,但这些都是JIT可以对代码做出的逻辑观察。也许在未来,一些JIT编译器甚至可能优化整个while循环并简单地将计数设置为reps,但这似乎不太可能。

答案 1 :(得分:1)

尝试预测现代语言的表现效率不高。

JVM经常被修改,以提高常见的可读结构的性能,相反,它会使不常见的,笨拙的代码变慢。

尽可能清楚地编写代码 - 然后如果您确实认为代码实际上被识别为太慢而无法通过书面规范,那么您可能需要手动调整某些区域 - 但这可能会涉及大而简单的想法,如对象缓存,调整JVM选项和消除真正愚蠢/错误的代码(错误的数据结构可能是巨大的,我曾经将ArrayList更改为LinkedList并将操作从10分钟减少到5秒,多线程发现B类网络的ping操作从8小时到几分钟进行了操作。