为高性能数据结构定义一组基本规则(java)

时间:2011-11-17 20:36:09

标签: java performance collections guava

我通常可以互换地使用向量/ arraylists,hashmaps / treemaps和其他java集合,但有时存在功能API要求(例如,在某些情况下我可能需要排序数据集)。

然而,最近,我发现需要将Java性能提升到我正在运行的某些算法的极限。

是否有一套高性能数据结构指南,我可以将其用作编码的基本规则?

我正在寻找一般规则,但在这种情况下,对以下问题的回答可能也非常有用:

  

1)我什么时候应该使用多维数组而不是嵌套数组   收藏?

     

2)向量与阵列列表 - 真的有性能差异吗?

     

3)集合API就像Google的集合,java技巧(如   反思和铸造),以及其他常见的java开发人员习惯用法   在负载很重的情况下减慢JVM的速度?

     

4)原语与常规对象(即Double vs double)是否会减慢   JVM在进行大量计算时?

     

5)是否有其他重要的指导方针来处理大型问题   需要高性能的java程序中的集合?

  • 注意:此时,我没有进行任何多线程...我发现一旦开始并行化,就会有其他限制。

8 个答案:

答案 0 :(得分:9)

首先应通过分析(时间和内存/对象使用)来解决所有性能问题。不要优化不影响代码性能的因素。有了这个警告,有一些一般的经验法则(应该通过分析测试!)

  

1)我何时应该使用多维数组而不是嵌套的集合?

当您不需要动态调整集合大小并且不需要将数据提供给需要集合的任何内容时,那么多维数组(实际上是数组的数组)可以更快。

  

2)向导与阵列列表 - 是否真的存在性能差异?

是。 Vector中的许多方法都是同步的,这很昂贵。如果您不是多线程,则避免使用Vector。即使你是,同步的粒度通常是错误的,你最好自己提供线程安全。

  3)集合API就像谷歌的集合,java技巧(如反射和转换),以及其他常见的Java开发人员习惯用法在负载过重时会降低JVM的速度吗?

反思很慢;垃圾收集很慢。你可以采取任何措施来避免这些事情的发生。

  

4)在进行大量计算时,原语与常规对象(即Double vs double)是否会减慢JVM的速度?

是。自动装箱/拆箱可以非常快速地产生大量垃圾。这一切都必须收集,这也将减慢您的计划。

  

5)是否有其他重要的指导方针来处理需要高性能的java程序中的大型集合?

首选本地方法变量进行字段访问。您可以通过搜索网络找到许多其他指南。但最重要的是要分析。

编辑:有很多性能提示here

答案 1 :(得分:8)

回答你的问题4)是的, Double vs double 肯定会改变表现

如果您拥有由基元组成的集合,那么您当然可以使用基元支持的集合,例如非常好的Trove API。通过避免不断的原始到对象和反之亦然(un)拳击,你可以节省内存和宝贵的时间。

到目前为止, Vector 类已成为过去。

答案 2 :(得分:3)

1)如果您不需要真正的动态调整大小,或者您可以将数据放入足够小的“最大大小”容器中,那么您将从阵列中获得比从集合中随机访问更好的性能,因为删除方法调用开销以及可能更多(取决于使用的集合)。

2)在我看来,应该考虑向量和哈希表几乎就像它们被弃用一样。它们是“线程安全的”,但对于大多数现实世界的场景,仅仅让数据结构本身是线程安全的是不够的;通常,您的应用程序逻辑也必须是此同步的一部分。 ArrayList,HashMap会表现得更好,因为它们没有同步块,99.9%的时间无法为你提供任何有用的东西。

3)Google的馆藏API很棒,没有真正的性能问题。反射肯定很慢,不应该在内循环中。

4)理想情况下,您希望避免在内部循环中对原语进行装箱/取消装箱。您可以找到专门针对基元的集合(即Trove集合http://trove.starlight-systems.com/)。

5)这取决于具体用途,我不会说有任何一般指导方针。只需确保在转换集合等时理解您在做什么。例如,确保在将列表转换为集合或类似内容时,不会克隆整个集合。

答案 3 :(得分:2)

  1. 我相信你应该使用Vector的唯一时间就是需要同步,但你可以在ArrayList上使用特殊的Syncronized thingy,所以我不想要Vector。始终使用ArrayList而不是LinkedList。它不同于常识,所以它必须是java的实现,但ArrayList要快得多。我曾经相信LinkedList所以我创建了以下测试:

    import java.util.ArrayList; import java.util.GregorianCalendar; import java.util.LinkedList; import java.util.List; import java.util.Random;

  2. / **  *  * /

    / **  * @author thom  *  * / 公共类ListTest {

    private ArrayList<Integer>      arrayList = new ArrayList<Integer>();
    private LinkedList<Integer>     linkedList = new LinkedList<Integer>();
    
    /**
     * 
     */
    public void test(){
        LinkedList<Integer> arrayTimes = new LinkedList<Integer>();
        LinkedList<Integer> linkedTimes = new LinkedList<Integer>();
    
        for(int ix = 0; ix < 100; ix ++){
            arrayList.clear();
            long start = new GregorianCalendar().getTimeInMillis();
            fillList(arrayList);
            long stop = new GregorianCalendar().getTimeInMillis();
            int elapsed = (int) (stop - start);
            arrayTimes.add(elapsed);
        }
    
        for(int ix = 0; ix < 100; ix ++){
            linkedList.clear();
            long start = new GregorianCalendar().getTimeInMillis();
            fillList(linkedList);
            long stop = new GregorianCalendar().getTimeInMillis();
            int elapsed = (int) (stop - start);
            linkedTimes.add(elapsed);
        }
    
        double arrayAvg = avg(arrayTimes);
        double linkedAvg = avg(linkedTimes);
    
        System.err.println("Adding 100,000 entries 100 times to linked list.");
        System.err.println("ArrayList elapsed time (ms.):" + arrayAvg);
        System.err.println("LinkedList elapsed time (ms.):" + linkedAvg);
    
        arrayTimes.clear();
        linkedTimes.clear();
    
        long start = new GregorianCalendar().getTimeInMillis();
        insertMiddle(arrayList);
        long stop = new GregorianCalendar().getTimeInMillis();
        int elapsed = (int) (stop - start);
    
        System.err.println();
        System.err.println("Inserting 1,000 entries to the middle of the list.");
        System.err.println("ArrayList elapsed time (ms.):" + elapsed);
    
        start = new GregorianCalendar().getTimeInMillis();
        insertMiddle(linkedList);
        stop = new GregorianCalendar().getTimeInMillis();
        elapsed = (int) (stop - start);
        System.err.println("LinkedList elapsed time (ms.):" + elapsed);
    
        start = new GregorianCalendar().getTimeInMillis();
        for(int ix = 0; ix < 100; ++ix){
            for(int jx = 0; jx < 100000; ++jx){
                arrayList.get(jx);
            }
        }
        stop = new GregorianCalendar().getTimeInMillis();
        elapsed = (int) (stop - start);
    
        System.err.println();
        System.err.println("Sequentially reading the list 100 times");
        System.err.println("ArrayList elapsed time (ms.):" + elapsed);
    
        start = new GregorianCalendar().getTimeInMillis();
        for(int ix = 0; ix < 100; ++ix){
            for(int jx = 0; jx < 100000; ++jx){
                linkedList.get(jx);
            }
        }
        stop = new GregorianCalendar().getTimeInMillis();
        elapsed = (int) (stop - start);
        System.err.println("LinkedList elapsed time (ms.):" + elapsed);
    
        Random rnd = new Random();
        start = new GregorianCalendar().getTimeInMillis();
        for(int ix = 0; ix < 100; ++ix){
            for(int jx = 0; jx < 100000; ++jx){
                int index = rnd.nextInt(100000);
                arrayList.get(index);
            }
        }
        stop = new GregorianCalendar().getTimeInMillis();
        elapsed = (int) (stop - start);
    
        System.err.println();
        System.err.println("Randomly reading the list 100 times");
        System.err.println("ArrayList elapsed time (ms.):" + elapsed);
    
        start = new GregorianCalendar().getTimeInMillis();
        for(int ix = 0; ix < 100; ++ix){
            for(int jx = 0; jx < 100000; ++jx){
                int index = rnd.nextInt(100000);
                linkedList.get(index);
            }
        }
        stop = new GregorianCalendar().getTimeInMillis();
        elapsed = (int) (stop - start);
        System.err.println("LinkedList elapsed time (ms.):" + elapsed);
    }
    
    /**
     * @param values
     */
    protected double avg(List<Integer> values){
        double sum = 0;
        for(int ix:values){
            sum += ix;
        }
    
        double result = sum / values.size();
        return result;
    }
    
    /**
     * @param list
     */
    protected void fillList(List<Integer> list){
        for(int ix = 0; ix < 100000; ix++){
            list.add(ix);
        }
    }
    
    /**
     * @param list
     */
    protected void insertMiddle(List<Integer> list){
        for(int ix = 0; ix < 1000; ix++){
            list.add(50000, ix);
        }
    }
    
    /**
     * @param args
     */
    public static void main(String[] args) {
        ListTest listTest = new ListTest();
        listTest.test();
    }
    
    }
    

    它产生了以下结果:

    Adding 100,000 entries 100 times to linked list.
    ArrayList elapsed time (ms.):2.78
    LinkedList elapsed time (ms.):12.24
    
    Inserting 1,000 entries to the middle of the list.
    ArrayList elapsed time (ms.):35
    LinkedList elapsed time (ms.):154
    
    Sequentially reading the list 100 times
    ArrayList elapsed time (ms.):94
    LinkedList elapsed time (ms.):748271
    
    Randomly reading the list 100 times
    ArrayList elapsed time (ms.):404
    LinkedList elapsed time (ms.):1158273
    

    有人请验证我的代码,以确保我没有做一些愚蠢的事情,但它表明ArrayList比LinkedList的速度更快。

    1. 反思肯定很慢。

    2. 基元的计算速度更快。小心自动拳击,因为它是一个性能打击。这很好,只要确定你了解成本。

答案 4 :(得分:1)

1)当您知道最大尺寸时,请使用数组。

2)向量具有同步方法,因此比ArrayLists慢。它们是有区别的。最近有使用Collections.synchronizedList而不是向量的tendention。

3)有一些“快速”集合的实现,例如http://labs.carrotsearch.com/hppc.html或Trove,其他What is the most efficient Java Collections library?

4)如果可以,请使用原语。 Wrappers带来了额外的开销。

5)想想你必须做什么,最多会采取什么行动,例如添加元素到set比arraylist慢,迭代arraylist比set更快。但是从arraylist中删除元素比在set中慢。如果可以使用数组 - 它们将比任何其他集合更快。当您必须使用集合时,但是您知道将插入多少元素,请使用具有初始大小的构造函数。

答案 5 :(得分:1)

恕我直言,首要的规则是为您的用例挑选正确的结构。

使用地图实现字典可能对性能(时间)有好处,因为需要花费大量内存(空间),而是使用Trie

哈希搜索(使用HashMap)很好但是如果你有一个有限数值范围的键,那么数组会做得更好。

我建议的唯一经验法则是在必须处理数据GB和/或微秒响应要求时设计自己的数据结构。

答案 6 :(得分:0)

您是否需要直接访问数据?如果需要,您现在可以确切了解对象的位置吗?如果你一直遍历集合以找出你需要的对象的位置,这需要一些时间(因此直接访问将是有利的)

自动装箱确实需要时间,因为你不能创建原始类型的集合,所以它们将一直自动装入他们的亲戚。

答案 7 :(得分:0)

另一个小技巧:

如果你使用非常大的集合,并且你事先知道(或可以估计)它们的大小,你应该使用允许你指定初始容量的构造函数。这避免了多个阵列分配。