【问题标题】:Java - LinkedList - Performance decreases with the number of different classes in itJava - LinkedList - 性能随着其中不同类的数量而降低
【发布时间】:2012-12-03 19:34:44
【问题描述】:

以下代码测量从接口 Handler 调用 100 次方法 handle(Object o) 所花费的时间(是的,这是质量差的分析):

package test;

import java.util.LinkedList;

public class Test {

    static int i = 0;

    private interface Handler {
        public void handle(Object o);
    }
    private static class SuperHandler implements Handler {
        public void handle(Object o) { i += 1; }
    }
    private static class NoSuperHandler implements Handler {
        public void handle(Object o) { i += 1; }
    }
    private static class LulSuperHandler implements Handler {
        public void handle(Object o) { i += 1; }
    }
    private static class LilSuperHandler implements Handler {
        public void handle(Object o) { i += 1; }
    }
    private static class LolSuperHandler implements Handler {
        public void handle(Object o) { i += 1; }
    }
    private static class LalSuperHandler implements Handler {
        public void handle(Object o) { i += 1; }
    }
    private static class LylSuperHandler implements Handler {
        public void handle(Object o) { i += 1; }
    }
    private static class LzlSuperHandler implements Handler {
        public void handle(Object o) { i += 1; }
    }

    public static void main(String[] args) {
        LinkedList<Handler> ll = new LinkedList<Handler>();
        for(int j = 0; j < 100; j++) {
            if((j % 8) == 0) ll.add(new SuperHandler());
            if((j % 8) == 1) ll.add(new NoSuperHandler());
            if((j % 8) == 2) ll.add(new LulSuperHandler());
            if((j % 8) == 3) ll.add(new LilSuperHandler());
            if((j % 8) == 4) ll.add(new LolSuperHandler());
            if((j % 8) == 5) ll.add(new LalSuperHandler());
            if((j % 8) == 6) ll.add(new LylSuperHandler());
            if((j % 8) == 7) ll.add(new LzlSuperHandler());
        }
        long begin = System.currentTimeMillis();
        for(int j = 0; j < 1000000; j++) for(Handler h: ll) h.handle(null);
        System.out.println("time in ms: " + (System.currentTimeMillis() - begin));
        System.out.println("i: " + i);
    }
}

事实上,如果 LinkedList 只包含一种 Handler,例如 SuperHandler,则执行时间比如果它们分别是 2、3 等不同类型的 Handler。而且每次我在列表中添加一种新的 Handler 时,性能都会下降。

例如,当我只更改这部分时,我会得到比上面更好的性能:

for(int j = 0; j < 100; j++) {
    if((j % 2) == 0) ll.add(new SuperHandler());
    if((j % 2) == 1) ll.add(new NoSuperHandler());
}

这里有特殊的优化操作吗? JAVA 架构中的哪些性能会降低?我的测试是错误的,因为未使用的 Handler 被编译器“删除”或“隐藏”了吗? (我使用的是 Linux Ubuntu - JAVA 1.7,来自 Oracle)

【问题讨论】:

    标签: java performance linked-list


    【解决方案1】:

    这里有特殊的优化操作吗?

    是的。 Hotspot 在处理虚方法方面非常聪明。如果一个接口只有几个实现,并且这些实现很小,它可以通过检查正确的类型并内联代码来避免完整的 vtable-lookup。

    当您有几个不同的实现时,它会回到 vtable 实现。 (Hotspot 足够聪明,可以撤消不再实用的优化。我很震惊这一切都挂在一起,但显然确实如此。)

    请注意,这与列表中有多少不同的类无关 - 这里还有更多内容。有关详细信息,请参阅彼得的答案。

    【讨论】:

    • AFAIK,HotSpot 中内联虚拟方法的限制为 2。我不确定仅加载类是否足以触发去优化。
    • @PeterLawrey:啊 - 感谢您的额外澄清。将编辑。
    • 查看我的答案以获取示例。我有相同的列表,其中在一个地方调用了 8 种不同的类型,而在另一个列表中排列了 8 种不同的调用以仅调用一种类型(但来自同一个列表)性能差异是巨大的。
    【解决方案2】:

    我同意 Jon 的回答,但我相信代码点调用的类型数量会有所不同。在下面的示例中,加载了所有 8 个类,并且针对列表中相同数量的元素运行相同的代码,除了一个列表有 8 个,另一个有 2 种不同的类型。

    import java.util.ArrayList;
    import java.util.LinkedList;
    import java.util.List;
    
    public class Test {
    
        static int i = 0;
    
        private interface Handler {
            public void handle();
        }
    
        public static void main(String[] args) {
            List<Handler> ll8 = new ArrayList<Handler>();
            for (int j = 0; j < 128; j += 8) {
                ll8.add(new SuperHandler());
                ll8.add(new NoSuperHandler());
                ll8.add(new LulSuperHandler());
                ll8.add(new LilSuperHandler());
                ll8.add(new LolSuperHandler());
                ll8.add(new LalSuperHandler());
                ll8.add(new LylSuperHandler());
                ll8.add(new LzlSuperHandler());
            }
            List<Handler> ll2 = new ArrayList<Handler>();
            for (int j = 0; j < 128; j += 2) {
                ll2.add(new SuperHandler());
                ll2.add(new NoSuperHandler());
            }
            for (int j = 0; j < 5; j++) {
                test8(ll8);
                test8a(ll8);
                test2(ll2);
            }
            System.out.println("i: " + i);
        }
    
        private static void test8(List<Handler> ll8) {
            long begin = System.nanoTime();
            for (int j = 0; j < 1000000; j++) for (Handler h : ll8) h.handle();
            System.out.println("8 classes, time in ms: " + (System.nanoTime() - begin) / 100000 / 10.0);
        }
    
        private static void test8a(List<Handler> ll8) {
            long begin = System.nanoTime();
            for (int j = 0; j < 1000000; j++)
                for (int k = 0; k < ll8.size(); k += 8) {
                    ll8.get(k + 0).handle();
                    ll8.get(k + 1).handle();
                    ll8.get(k + 2).handle();
                    ll8.get(k + 3).handle();
                    ll8.get(k + 4).handle();
                    ll8.get(k + 5).handle();
                    ll8.get(k + 6).handle();
                    ll8.get(k + 7).handle();
                }
            System.out.println("8 classes unrolled, time in ms: " + (System.nanoTime() - begin) / 100000 / 10.0);
        }
    
        private static void test2(List<Handler> ll2) {
            long begin = System.nanoTime();
            for (int j = 0; j < 1000000; j++) for (Handler h : ll2) h.handle();
            System.out.println("2 classes, time in ms: " + (System.nanoTime() - begin) / 100000 / 10.0);
        }
    
        private static class SuperHandler implements Handler {
            public void handle() { i += 1; }
        }
    
        private static class NoSuperHandler implements Handler {
            public void handle() { i += 1; }
        }
    
        private static class LulSuperHandler implements Handler {
            public void handle() { i += 1; }
        }
    
        private static class LilSuperHandler implements Handler {
            public void handle() { i += 1; }
        }
    
        private static class LolSuperHandler implements Handler {
            public void handle() { i += 1; }
        }
    
        private static class LalSuperHandler implements Handler {
            public void handle() { i += 1; }
        }
    
        private static class LylSuperHandler implements Handler {
            public void handle() { i += 1; }
        }
    
        private static class LzlSuperHandler implements Handler {
            public void handle() { i += 1; }
        }
    }
    

    打印

    8 classes, time in ms: 1467.9
    8 classes unrolled, time in ms: 144.7
    2 classes, time in ms: 515.8
    8 classes, time in ms: 1455.1
    8 classes unrolled, time in ms: 126.2
    2 classes, time in ms: 509.6
    8 classes, time in ms: 1234.1
    8 classes unrolled, time in ms: 107.8
    2 classes, time in ms: 274.3
    8 classes, time in ms: 1212.0
    8 classes unrolled, time in ms: 108.1
    2 classes, time in ms: 273.0
    8 classes, time in ms: 1208.8
    8 classes unrolled, time in ms: 107.8
    2 classes, time in ms: 274.5
    i: 1920000000
    

    【讨论】:

    • 非常有趣的答案和相关的代码,谢谢(也有 2 个限制)。我不得不说我对这些优化印象深刻。您的代码按 8 读取数组。如果您使用 4 或 8 的任何倍数,似乎会有优化。
    • 在展开的情况下,每次调用的类型始终相同(但每次调用不同)
    猜你喜欢
    • 2018-09-22
    • 2018-06-08
    • 2020-01-31
    • 2012-09-12
    • 2020-07-04
    • 2020-02-19
    • 1970-01-01
    • 2016-11-06
    • 2021-07-17
    相关资源
    最近更新 更多