Vector与ArrayList的性能

每个人都说应该使用矢量因为性能(导致Vector在每次操作和东西之后同步)。 我写了一个简单的测试:

import java.util.ArrayList;
import java.util.Date;
import java.util.Vector;

public class ComparePerformance {

    public static void main(String[] args) {
        ArrayList<Integer> list = new ArrayList<Integer>();
        Vector<Integer> vector = new Vector<Integer>();

        int size = 10000000;
        int listSum = 0;
        int vectorSum = 0;

        long startList = new Date().getTime();
        for (int i = 0; i < size; i++) {
            list.add(new Integer(1));
        }
        for (Integer integer : list) {
            listSum += integer;
        }
        long endList = new Date().getTime();
        System.out.println("List time: " + (endList - startList));

        long startVector = new Date().getTime();
        for (int i = 0; i < size; i++) {
            vector.add(new Integer(1));
        }
        for (Integer integer : list) {
            vectorSum += integer;
        }
        long endVector = new Date().getTime();
        System.out.println("Vector time: " + (endVector - startVector));
    }
}

结果如下:

List time: 4360
Vector time: 4103

基于此,似乎在迭代和读取时的Vector性能稍好一些。 也许这是一个愚蠢的问题,或者我做了错误的假设 - 有人可以解释这个吗?


你写了一个天真的微基准。 微软在JVM上的标记是非常棘手的事情,要枚举所有的陷阱并不容易,但这里有一些经典的:

  • 你必须预热代码;
  • 您必须控制垃圾收集暂停;
  • System.currentTimeMillis是不精确的,但你甚至不知道这个方法(你的new Date().getTime()是等价的,但是更慢)。
  • 如果您想正确执行此操作,请查看Oracle的jmh工具或Google的Caliper。

    我的测试结果

    由于我有兴趣jmh查看这些数字,因此这里是jmh的输出。 首先,测试代码:

    public class Benchmark1
    {
      static Integer[] ints = new Integer[0];
      static {
        final List<Integer> list = new ArrayList(asList(1,2,3,4,5,6,7,8,9,10));
        for (int i = 0; i < 5; i++) list.addAll(list);
        ints = list.toArray(ints);
      }
      static List<Integer> intList = Arrays.asList(ints);
      static Vector<Integer> vec = new Vector<Integer>(intList);
      static List<Integer> list = new ArrayList<Integer>(intList);
    
      @GenerateMicroBenchmark
      public Vector<Integer> testVectorAdd() {
        final Vector<Integer> v = new Vector<Integer>();
        for (Integer i : ints) v.add(i);
        return v;
      }
      @GenerateMicroBenchmark
      public long testVectorTraverse() {
        long sum = (long)Math.random()*10;
        for (int i = 0; i < vec.size(); i++) sum += vec.get(i);
        return sum;
      }
      @GenerateMicroBenchmark
      public List<Integer> testArrayListAdd() {
        final List<Integer> l = new ArrayList<Integer>();
        for (Integer i : ints) l.add(i);
        return l;
      }
      @GenerateMicroBenchmark
      public long testArrayListTraverse() {
        long sum = (long)Math.random()*10;
        for (int i = 0; i < list.size(); i++) sum += list.get(i);
        return sum;
      }
    }
    

    结果是:

    testArrayListAdd          234.896  ops/msec
    testVectorAdd             274.886  ops/msec
    testArrayListTraverse    1718.711  ops/msec
    testVectorTraverse         34.843  ops/msec
    

    请注意以下几点:

  • ...add方法我创建一个新的本地集合。 JIT编译器使用这个事实并且避免了对Vector方法的锁定 - 因此性能几乎相同;
  • ...traverse我从全局集合中读取的...traverse方法; 这些锁不能被消除,这就是Vector出现真正性能损失的地方。
  • 从这个主要应该是:在JVM上的性能模型是非常复杂的,有时甚至不稳定。 从microbenchmarks推断,即使他们完成所有应有的注意,可能导致对生产系统性能的危险错误的预测。


    我同意Marko关于使用Caliper的看法,这是一个很棒的框架。

    但是如果你组织好你的基准测试,你可以自己完成一部分工作:

    public class ComparePerformance {
    
        private static final int SIZE = 1000000;
        private static final int RUNS = 500;
        private static final Integer ONE = Integer.valueOf(1);
    
        static class Run {
            private final List<Integer> list;
    
            Run(final List<Integer> list) {
                this.list = list;
            }
    
            public long perform() {
                long oldNanos = System.nanoTime();
                for (int i = 0; i < SIZE; i++) {
                    list.add(ONE);
                }
    
                return System.nanoTime() - oldNanos;
            }
        }
    
        public static void main(final String[] args) {
    
            long arrayListTotal = 0L;
            long vectorTotal = 0L;
            for (int i = 0; i < RUNS; i++) {
                if (i % 50 == 49) {
                    System.out.println("Run " + (i + 1));
                }
    
                arrayListTotal += new Run(new ArrayList<Integer>()).perform();
                vectorTotal += new Run(new Vector<Integer>()).perform();
            }
    
            System.out.println();
    
    
            System.out.println("Runs: "+RUNS+", list size: "+SIZE);
            output(arrayListTotal, "List");
            output(vectorTotal, "Vector");
        }
    
        private static void output(final long value, final String name) {
            System.out.println(name + " total time: " + value + " (" + TimeUnit.NANOSECONDS.toMillis(value) + " " + "ms)");
    
            long avg = value / RUNS;
            System.out.println(name + " average time: " + avg + " (" + TimeUnit.NANOSECONDS.toMillis(avg) + " " + "ms)");
        }
    }
    

    关键部分经常运行您的代码。 此外,删除与基准无关的内容。 重复使用整数而不是创建新的。

    上面的基准代码在我的机器上创建了这个输出:

    Runs: 500, list size: 1000000
    List total time: 3524708559 (3524 ms)
    List average time: 7049417 (7 ms)
    Vector total time: 6459070419 (6459 ms)
    Vector average time: 12918140 (12 ms)
    

    我会说,应该给你一个性能差异的想法。


    正如Marko Topolnik所说,很难写出正确的微观基准并正确解释结果。 有关于这个问题的好文章是可用的。

    根据我的经验和我所知道的实现,我使用这个经验法则:

  • 使用ArrayList
  • 如果集合必须同步,请考虑向量的使用。 (我永远不会使用它,因为还有其他解决方案用于同步,并发和并行编程)
  • 如果集合中有很多元素,并且列表中有频繁的插入或删除操作(不是最后),则使用LinkedList
  • 大多数藏品不包含很多元素,花费更多精力在他们身上会浪费时间。 在scala中也有并行集合,它们并行执行一些操作。 也许有一些可用于纯Java的东西。

    尽可能使用List接口来隐藏实现细节并尝试添加显示原因的注释为什么选择了特定的实现。

    链接地址: http://www.djcxy.com/p/76101.html

    上一篇: Vector vs ArrayList performance

    下一篇: Change from ArrayList to Vector