问一个JAVA代码性能问题

1: HashMap<String, String> test = new HashMap<>();
2: Map<String, String> test = new HashMap<>();

只进行put、get操作
请问1的性能会优于2吗?为什么?

阅读 4.1k
4 个回答
    HashMap<String, String> map1 = new HashMap<>();
    Map<String, String> map2 = new HashMap<>();
    map1.put("a", "b");
    map2.put("a", "b");
    

=>

16  aload_1 [map1]
17  ldc <String "a"> [160]
19  ldc <String "b"> [162]
21  invokevirtual java.util.HashMap.put(java.lang.Object, java.lang.Object) : java.lang.Object [164]
24  pop
25  aload_2 [map2]
26  ldc <String "a"> [160]
28  ldc <String "b"> [162]
30  invokeinterface java.util.Map.put(java.lang.Object, java.lang.Object) : java.lang.Object [168] [nargs: 3]

这里的测试中, http://bobah.net/book/export/html/55
invokeinterface可能慢38%

http://stackoverflow.com/questions/1504633/what-is-the-point-of-invokeinterface
这里有解释

对于这个问题,一般的答案是“差不多,没有区别”;
而钻牛角尖的答案是“2的性能比1稍好”;

下面的代码:

HashMap<String, String> m1 = new HashMap<>();
        m1.put("test", "test");
        m1.get("test");

        Map<String, String> m2 = new HashMap<>();
        m2.put("test", "test");
        m2.get("test");

编译成字节码后对应的指令是:

0: new           #16                 // class java/util/HashMap
         3: dup           
         4: invokespecial #18                 // Method java/util/HashMap."<init>":()V
         7: astore_1      
         8: aload_1       
         9: ldc           #19                 // String test
        11: ldc           #19                 // String test
        13: invokevirtual #21                 // Method java/util/HashMap.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
        16: pop           
        17: aload_1       
        18: ldc           #19                 // String test
        20: invokevirtual #25                 // Method java/util/HashMap.get:(Ljava/lang/Object;)Ljava/lang/Object;
        23: pop           
        24: new           #16                 // class java/util/HashMap
        27: dup           
        28: invokespecial #18                 // Method java/util/HashMap."<init>":()V
        31: astore_2      
        32: aload_2       
        33: ldc           #19                 // String test
        35: ldc           #19                 // String test
        37: invokeinterface #29,  3           // InterfaceMethod java/util/Map.put:(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;
        42: pop           
        43: aload_2       
        44: ldc           #19                 // String test
        46: invokeinterface #32,  2           // InterfaceMethod java/util/Map.get:(Ljava/lang/Object;)Ljava/lang/Object;
        51: pop           
        52: return

可见情况1的map的put/get操作是用invokevirtual指令完成的;
而情况2的map的put/get操作是用invokeinterface指令完成的;

而论实现的话,invokevirtual的性能略优于invokeinterface, 因此硬要说谁性能好的话那就是2;

最后提醒一下,在java编程过程中,任何jvm指令我们都应该看作是差不多一样的常数级时间开销,哪怕它是invokedynamic,这才能为我们的上层算法、逻辑优化带来统一、无干扰的视角;
为了钻牛角尖地挑选jvm指令而改变java代码的写法是不理智的,且其结论也是不稳定的 —— 它可能会随着jvm升级换代而变化的, 而且为了这种“性能提升”而带来的代码改动导致可读性、可维护性降低也是得不偿失的

根据可操作方法论,2的性能优于1

通常来说,我们应该优先用interface:

Map<String, String> test = new HashMap<>();
// is better than
HashMap<String, String> test = new HashMap<>();

请参阅:https://stackoverflow.com/que...
关于性能,首先我们要明白写一个正确的性能测试并不是件容易的事,如果确实有性能的担忧,最好用实际的用例来测试;

public class InvokevirtualVsInvokeinterface {
    private static interface I {
        public int getInteger();
    }

    private static class A implements I {
        @Override
        public int getInteger() {
            return 0;
        }
    }

    static volatile I i = new A();
    static volatile A a = new A();

    public static void main(String[] args) {

        {
            long tm1 = System.nanoTime();
            for (int k = 0; k < 100000000; ++k) {
                i.getInteger();
            }
            long tm2 = System.nanoTime();
            System.out.println("invokeinterface took " + (Math.abs(tm2 - tm1) / 1000) + " us");
        }

        {
            long tm1 = System.nanoTime();
            for (int k = 0; k < 100000000; ++k) {
                a.getInteger();
            }
            long tm2 = System.nanoTime();
            System.out.println("invokevirtual took " + (Math.abs(tm2 - tm1) / 1000) + " us");
        }

        {
            Map<String, Integer> map = new HashMap<>();
            long tm1 = System.nanoTime();
            for (int k = 0; k < 100000000; ++k) {
                map.put("a", 1);
            }
            long tm2 = System.nanoTime();
            System.out.println("invokeinterface-Map took " + (Math.abs(tm2 - tm1) / 1000) + " us");
        }

        {
            HashMap<String, Integer> map = new HashMap<>();
            long tm1 = System.nanoTime();
            for (int k = 0; k < 100000000; ++k) {
                map.put("a", 1);
            }
            long tm2 = System.nanoTime();
            System.out.println("invokevirtual-HashMap took " + (Math.abs(tm2 - tm1) / 1000) + " us");
        }
    }
}

通过运行上面的代码,在我的机子上我得到两个关于invokeinterface和invokevirtual绝然不同的结论:

  1. 通过比较前面两个测试结果,得到invokevirtual比invokeinterface快的结论

  2. 通过比较后面两个测试结果,得到invokevirtual比invokeinterface慢的结论

而真正的原因是上面所有的测试本身就是不正确,通过比较它们的测试结果得到的结论自然也就不正确。

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题