如何减少此循环的执行时间(java)

时间:2010-10-23 21:00:30

标签: java

我有一段代码似乎花费了不寻常的时间来执行。我需要尽可能地降低执行速度。

基本上,代码执行以下操作。我创建了一个对象数组[10] [10] [10]。该对象包含一个数字列表,如下所示:

class MyClass{
  ArrayList<Integer> numberList;
      public MyClass(){
          numberList= new ArrayList <Integer> ();
      }
   }


      MyClass storage[][][] = new MyClass[10][10][10];

然后我有以下代码将数字添加到列表

 for(int i =0; i < 200000;i++){
       for(int j = 0; j < 10; j++){
        for(int k = 0; k < 10; k++){
         for(int l = 0; l < 10; l++){            
                   storage[j][k][l].numberList.add(i);
         }
        }
       }
      }

我很确定绝大多数执行时间来自以下行

 storage[j][k][l].numberList.add(i);

更具体地说,它是.add(i)。

我是Java的新手,我只熟悉C ++。如果ArrayList类似于C ++中的列表,那么肯定在末尾添加一个元素需要非常少的CPU时间?是不是因为我做了很多添加操作(可能是一百万)?

我想问的另一件事是我可以通过使用线程加快速度吗? (假设双核处理器有4个线程)我想我可以创建4个线程,每个线程处理50,000个块。但是,我不确定同步。据推测,我必须在存储[] [] []上进行一些互斥。我需要写

synchronized(storage)

或者这样会好吗?

synchronized(storage[j][k][l])

任何帮助非常感谢

此致

7 个答案:

答案 0 :(得分:7)

从不,永远使用默认的Java包装类,在处理内存中的数千万个数据时,也可以存储为基元。

这是射击自己的最可靠的方法:去过那里,做到了。

new ArrayList <Integer>

可以用Trove的TIntArrayList替换琐事

new TIntArrayList

你下载了Trove,它基本上是一个单行更改,可以在做你正在做的事情时节省很多的内存。

为了帮助解决问题:

    final int n = 10000000;

    final List<Integer> l1 = new ArrayList<Integer>( n );
    for (int i = 0; i < n; i++) {
        l1.add( i );
    }

    final TIntArrayList l2 = new TIntArrayList( n );       
    for (int i = 0; i < n; i++) {
        l2.add( i );
    }

第一个循环,使用基于原语的非语义默认Java包装器来保存1000万个整数,在我的机器上执行需要4320毫秒。

第二个需要41毫秒。

这样,两个数量级更快。

天真的态度是思考:“两者都是O(1)”

真相是:两者都是O(1),但我正在采用任何一天运行两个数量级的版本。

答案 1 :(得分:2)

使用new ArrayList(capacity)构造函数。在您的情况下capacity = 200000

如果您没有使用预定义的容量初始化ArrayList,则必须不时扩展,这有效地将支持列表的现有阵列复制到一个新的更大的阵列中。

如果指定初始容量,ArrayList将从一开始就由足够大的数组支持,并且不会进行复制。

如果你在另一个位置使用该类,其中200000不是所需的大小,你可以在另一个循环中调用ArrayList.ensureCapacity(capacity),这将做同样的事情 - 创建一个足够大的数组来保存所有数据一遍又一遍地复制它。

最后,我认为你应该能够完全避免填充这个结构。如果它真的像您的简化示例中那样可预测,您可以按需填写它。

答案 2 :(得分:1)

首先,学会对你的程序运行一个分析器,以确定瓶颈在哪里。一个可能是Sun 6 JDK中的jvisualvm。

我相信你认为问题与add()有关是正确的。

我认为问题在于ArrayLists()需要增长。尝试使用new ArrayList(200)形式(或任何合理的最终尺寸)并再次测量。

答案 3 :(得分:1)

基本上,你似乎在填充1000个数组,其数字从0到200000.我会填充一个ArrayList,然后只使用复制构造函数来填补其余部分:

class MyClass{
  List<Integer> numberList;
  public MyClass(){ ... }
  // Copy constructor
  public MyClass(ArrayList<Integer> otherList){
    numberList= new ArrayList<Integer>(otherList);
  }
}

MyClass storage[][][] = new MyClass[10][10][10];
List<Integer> prototypeList= new ArrayList<Integer>(200000);

for(int i =0; i < 200000;i++){
  prototypeList.add(i);
}

for(int j = 0; j < 10; j++){
  for(int k = 0; k < 10; k++){
    for(int l = 0; l < 10; l++){            
      storage[j][k][l] = new MyClass(prototypeList);
    }
  }
}

这样做的好处是可以消除列表的大小调整,并使用连续的内存块(这可以消除内存缓存命中 - 在原始循环中,您可以快速连续访问不同的列表对象,这肯定不能完全适合同一个内存缓存)。

答案 4 :(得分:1)

詹姆斯,

请记住,您提供了简化的代码;

  1. 该类需要将数据成员封装在MyClass上的方法后面。将“numberList”设为私有,并添加方法public void add(int i){...}。这意味着numberList的类型可以自由更改 - 封装是好的OO 的黄金法则#1。

  2. 将数据成员设为私有,您可以将数据类型的类型从ArrayList<Integer> numberList;更改为int [] numberList。我非常确定数组访问将比ArrayList更快。在构造函数中初始化数组。当然,这仅在元素数量始终为200,000或构造函数中传递的某个固定大小时才有效。此外,拥有一个int数组将比ArrayList小得多(在RAM中) - 每个Integer都是一个完整的对象,带有来自Object的行李。较小的RAM占用空间(在您的示例中)会减少可能需要的任何磁盘交换。

  3. 不要担心展开任何小的内部循环,JVM会自动,更有效地执行这些低级优化,而不会污染您的代码。

答案 5 :(得分:0)

LinkedList将等同于C ++中的列表

我认为ArrayList与std :: vector类似。

答案 6 :(得分:0)

您可能希望使用预期容量分配ArrayList。这样它只会被分配一次。

在你的情况下:

numberList= new ArrayList <Integer> ( 200000 );