我有一段代码似乎花费了不寻常的时间来执行。我需要尽可能地降低执行速度。
基本上,代码执行以下操作。我创建了一个对象数组[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])
任何帮助非常感谢
此致
答案 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)
詹姆斯,
请记住,您提供了简化的代码;
该类需要将数据成员封装在MyClass上的方法后面。将“numberList”设为私有,并添加方法public void add(int i){...}。这意味着numberList的类型可以自由更改 - 封装是好的OO 的黄金法则#1。
将数据成员设为私有,您可以将数据类型的类型从ArrayList<Integer> numberList;
更改为int [] numberList
。我非常确定数组访问将比ArrayList更快。在构造函数中初始化数组。当然,这仅在元素数量始终为200,000或构造函数中传递的某个固定大小时才有效。此外,拥有一个int数组将比ArrayList小得多(在RAM中) - 每个Integer都是一个完整的对象,带有来自Object的行李。较小的RAM占用空间(在您的示例中)会减少可能需要的任何磁盘交换。
不要担心展开任何小的内部循环,JVM会自动,更有效地执行这些低级优化,而不会污染您的代码。
答案 5 :(得分:0)
LinkedList将等同于C ++中的列表
我认为ArrayList与std :: vector类似。
答案 6 :(得分:0)
您可能希望使用预期容量分配ArrayList。这样它只会被分配一次。
在你的情况下:
numberList= new ArrayList <Integer> ( 200000 );