有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

性能Java jitting低效代码如何比高效代码运行得更快?

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

我希望Foo1Foo2快,但是在实践中,Foo2始终比Foo1快40%。JVM如何优化代码,使Foo2Foo1运行得更快

一些细节

  • 测试是用java -server CompositionTest执行的
  • 使用java -client CompositionTest运行测试会产生预期的结果,即Foo2Foo1
  • 切换循环的顺序没有什么区别
  • 结果在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);
    }
}

共 (1) 个答案

  1. # 1 楼答案

    你的microbench标记毫无意义。在我的电脑上,每个循环的代码运行时间约为8毫秒。。。要获得任何有意义的数字,基准测试可能至少应该运行一秒钟

    当两种方法都运行大约一秒钟(提示,您需要多次重复)时,我发现两种方法的运行时间是相同的

    对此可能的解释是,JIT编译器已经注意到您的间接寻址是无意义的,并对其进行了优化(或者至少将方法调用内联),以便在两个循环中执行的代码是相同的

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

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