0

我运行我的分析器程序进行了 1000 次迭代,以计算插入的Empty ArrayList平均时间和插入的平均时间Non-Empty ArrayList。我运行我的探查器程序ArrayList<Item>Item类在哪里

class Item{
  int id;
  String name;
}

探查器程序类似于

main(){
  final int iterations = 1000;

  /**** Empty List Insert ****/
  int id = 0;
  long[] timerecords = new long[iterations];
  ArrayList<Item> list = new ArrayList<>();
  for (int i = 0; i < iterations; i++) {
      long stime = System.nanoTime(); //Start Time
      list.add(new Item(id, "A"));
      long elapsedTime = System.nanoTime() - stime; //Elapsed time
      timerecords[i] = elapsedTime; //Record Elapsed time

      //reset
      list.clear();
  }
  System.out.println("Empty List Insert Takes = " + getAverageTime(timerecords) + " nanoseconds");

  /**** Non-Empty List Insert ****/
  list = new ArrayList<>();
  timerecords = new long[iterations];
  //Insert some Items
  list.add(new Item(id++, "B")); list.add(new Item(id++, "R"));
  list.add(new Item(id++, "H")); list.add(new Item(id++, "C")); 

  for (int i = 0; i < iterations; i++) {    
        Item item = new Item(id, "A");
        long stime = System.nanoTime(); //Start Time
        list.add(item);
        long elapsedTime = System.nanoTime() - stime; //Elapsed time
        timerecords[i] = elapsedTime; //Record Elapsed time

        //reset
        list.remove(item);
  }
  System.out.println("Non-Empty List Insert Takes = " + getAverageTime(timerecords) + " nanoseconds");
}

wheregetAverageTime(long [] timerecords)方法以双倍的形式返回平均时间。多次运行该程序后,我观察到插入空 ArrayList 比插入非空 ArrayList 需要更多时间。一种这样的运行输出是

Empty List Insert Takes = 1027.781 nanoseconds
Non-Empty List Insert Takes = 578.825 nanoseconds

这背后的原因是什么?

4

4 回答 4

2

不同之处在于实例创建(new Item())。

  long stime = System.nanoTime(); //Start Time
  list.add(new Item(id, "A")); // at this point two things are happening
  // creation of instance & addition into ArrayList
  // both takes their own time.

  long elapsedTime = System.nanoTime() - stime; //Elapsed time

您创建 stime 变量的点是计时器开始的点。您肯定会在列表中添加新项目。但在第一种情况下,在你做之前 System.nanoTime(); 您在添加时(在分配时间变量之后)在 Item 上调用 new 运算符,这会花费额外的时间。而在第二种情况下,你是在你做 System.nanoTime(); 之前做的。

    Item item = new Item(id, "A");
    long stime = System.nanoTime(); //Start Time
    list.add(item);
    long elapsedTime = System.nanoTime() - stime; //Elapsed time

因此,仅捕获将项目对象添加到 ArrayList 时间。

于 2015-03-07T09:42:12.237 回答
1

如果您查看的实现ArrayList- 一个空的 ArrayList 以一个空数组开头来存储数据 - 这很容易。

但是当您第一次调用该add()方法时,需要复制该数组 - 因此需要分配内存。和方法试图最小化 的创建ensureCapacity(),但只要内部 Array 大小仍然很小,这个数组越来越频繁地必须增加,这可能会导致持续时间增加。grow()Arrays.copyOf()

这是ArrayList.grow(int)从 1.7 开始的:

  private void grow(int minCapacity) {
    int oldCapacity = this.elementData.length;
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    if(newCapacity - minCapacity < 0) {
      newCapacity = minCapacity;
    }

    if(newCapacity - 2147483639 > 0) {
      newCapacity = hugeCapacity(minCapacity);
    }

    this.elementData = Arrays.copyOf(this.elementData, newCapacity);
  }

您没有测量创建非空 ArrayLIst 所花费的时间,是吗?

于 2015-03-07T09:31:48.867 回答
1

我很感兴趣,所以我对其进行了一些调试。效果来自两个不同的来源:

正如@Alexander 已经指出的那样,不断增加 Arraylist 大小的容量会损害您的测试。要“解决”这个问题,您可以创建具有足够配置的 ArrayLists,例如:

ArrayList<Item> list = new ArrayList<>(iterations * 2);

即使在这样做之后,您也可以观察到您的第一个和第二个测试用例之间的性能差异 100-300%。其原因与 Java 内部有关,即 ClassLoading 和 JIT。通过在第一次测试之前进行一次热身,如下所示:

    int rounds = 10000;
    long[] t = new long[rounds];
    ArrayList<Item> warmUpList = new ArrayList<>(rounds);
    for (int i = 0; i < rounds; i++) {
        warmUpList.add(new Item(0, "A"));
        long stime = System.nanoTime(); // Start Time
        long elapsedTime = System.nanoTime() - stime; // Elapsed time
        t[i] = elapsedTime; // Record Elapsed time
    }

您可以使用优化器。通过将“round”变量的值从 100 增加到 10.000,您可以看到您自己的测试如何获得越来越好的性能,并且比以相同的速度稳定。

于 2015-03-07T09:56:08.607 回答
1

使用 can 测量每个单个插入System.nanoTime()会导致不可靠的值。最好对System.currentTimeMillis()整个循环执行相同的操作并对其进行测量。

此外,ArrayList 中的插入时间会受到内部数组增长次数的影响。因此,插入带有小内部数组的空 ArrayList 会导致许多增长。插入带有部分为空的大内部数组的非空 ArrayList 可能不会导致增长,但如果发生增长,它会比空数组慢,因为您必须复制更大的数组。

于 2015-03-07T09:56:42.643 回答