9

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

我希望Foo1比 快Foo2,但在实践中,Foo2始终比 快 40% Foo1。JVM 是如何优化代码以使其Foo2运行速度快于Foo1?

一些细节

  • 测试是用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);
    }
}
4

2 回答 2

2

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

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

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

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

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

于 2012-05-18T16:22:33.303 回答
1

试图预测现代语言的性能并不是很有效率。

JVM 不断被修改以提高常见、可读结构的性能,相比之下,这会使不常见、笨拙的代码变慢。

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

于 2012-05-18T15:21:56.990 回答