Java:ArrayList瓶颈

时间:2010-03-24 00:09:03

标签: java optimization arraylist

在分析计算数千个元素的层次聚类的java应用程序时,我意识到ArrayList.get占用了执行集群化部分所需CPU的一半。

算法搜索两个更相似的元素(所以它是O(n *(n + 1)/ 2)),这里是伪代码:

int currentMax = 0.0f
for (int i = 0 to n)
  for (int j = i to n)
    get content i-th and j-th
      if their similarity > currentMax
        update currentMax

merge the two clusters

如此有效地涉及很多ArrayList.get

有更快的方法吗?我虽然因为ArrayList应该是一个线性的引用数组,它应该是最快的方式,也许我不能做任何事情,因为有太多get s ...但也许我错了。我不认为使用HashMap可以工作,因为我需要在每次迭代时都能得到它们map.values()应该由ArrayList支持..

否则我应该尝试更优化的其他集合库吗?就像google的那个,或apache one ..

修改

你在某种程度上证实了我的怀疑:(

我是否会在尝试并行化的过程中获得性能提升?也许使用一组执行程序来计算多对夫妇的相似性......但我不知道数据结构上的同步和锁定是否最终会减慢它的速度。

使用两个内容的标签映射的点积计算相似度。地图有两个HashMap<Tag, Float> ..此外,我已经在TLongFloatHashMap(来自 Trove 集合)中缓存相似性,以避免在以后的迭代中重新计算{{1} } key被计算为两个内容的哈希码(对于该对而言是唯一的Long)所以其他所有内容都已经调整好了。

EDIT2:

我会发布一些代码只是为了让你更好地理解..这用于计算用于存储两个元素之间相似性的哈希值:

hash(c1, c2) == hash(c2, c1)

这是计算相关性的方式:

private long computeKey(int h1, int h2) {   
        if (h1 < h2) {
            int swap = h1;
            h1 = h2;
            h2 = swap;
        }           
        return ((long)h1) << 32 | h2;
    }

这就是算法扫描内容的方式:

float correlation(Map<Tag, Float> map1, Map<Tag, Float>map2, HierarchNode n1, HierarchNode n2) {    
        long key = computeKey(n1.hashCode, n2.hashCode);

        if (cache.contains(key)) {
            ++hitCounter;
            return cache.get(key);
        }
        else {      
            float corr = 0.0f;

            Set<Map.Entry<Tag, Float>> entries;
            Map<Tag, Float> curMap;

            if (map1.size() < map2.size()) {
                entries = map1.entrySet();
                curMap = map2;
            }
            else {              
                entries = map2.entrySet();
                curMap = map1;
            }

            for (Map.Entry<Tag, Float> ee : entries) {
                Float f2 = curMap.get(ee.getKey());

                if (f2 != null)
                    corr += ee.getValue()*f2;
            }

            cache.put(key, corr);               
            return corr;
        }
    }

我只使用矩阵来存储所有值,但是在每次迭代时,从列表中删除最相似的项目并添加一个新项目(根据所选择的两个具有新的标记映射)

9 个答案:

答案 0 :(得分:2)

你拥有的算法是O(n²)。除非你有办法让你的算法比做成对比较做得更好,否则性能不太可能明显改善。 : - (

答案 1 :(得分:2)

冒着明显的风险,你可以通过使用这个伪代码来加快速度:

int currentMax = 0.0f
for (int i = 0 to n)
  get content i-th
  for (int j = i to n)
    get content j-th
      if their similarity > currentMax
        update currentMax

merge the two clusters

但它仍然是O(n²)。如果您需要将每个元素与每个其他元素进行比较以找出哪个元素最接近,那么您无法击败O(n²)

也就是说,如果你多次调用它,那么可以在可排序的地图中缓存这些结果时找到优化。

编辑:如果相似性相当简单(例如,高度等一维值),您可能首先可以对数组中的项进行排序,这样元素[0]最类似于元素[1]与元素[0]或元素[2]最相似。在这种情况下,您可以将速度提高到O(n lg n)

EDIT2:鉴于您的相关代码,您的基准测试结果非常可疑。我无法想象这两种情况比调用相关代码所花费的时间更多(即使假设缓存在绝大多数情况下都被命中),这也称为O(n²)次。如果get()是瓶颈,那么spong首先要将这些转换为数组。

答案 2 :(得分:2)

ArrayList.get是一个if语句,后跟一个数组访问。在那里优化并不多。 ArrayList.get占用执行时间的一半,因为你没有做任何其他事情。所花费时间的重要因素是迭代次数而不是for循环中的内容。

答案 3 :(得分:2)

没有O(n *(n + 1)/ 2)这样的东西。您的算法是O(n 2 )。有关更详细的说明,请参阅Plain english explanation of Big O

Ben是正确的:您可以通过在内循环之外获取 i -th元素来减少get()次调用。

你真正想要的是O(n 2 )的改进,这需要能够对元素做出额外的假设。这取决于你所说的“相似性”。

两种常见方法:

  • 对列表进行排序并合并。总的来说,这是O(n log n);
  • 将一个列表放入某种具有(接近)常量查找的Map中。这可以将算法减少到O(n)和O(n log n)之间的任何位置,具体取决于Map的类型和遍历的性质。

但这一切都取决于你所说的“相似性”。

答案 4 :(得分:1)

除了算法效率之外,你多次调用get。目前get被称为2*size*sizesize+size*size/2次。它应该被称为get次。这只会改变常数,但在我看来,你只需要调用for (int j = 0; j < clusters.size(); ++j) { skip = false; HierarchNode jnode = clusters.get(j); for (int k = j+1; k < clusters.size(); ++k) { HierarchNode knode = clusters.get(k); float r = correlation(knode.tags, jnode.tags, knode, jnode); ... etc ... 大约四分之一的当前时间。

尝试:

clusters.size()

根据HierarchNode[] clusterArr = clusters.toArray(new HierarchNode[clusters.size()]); 的大小,您可以通过执行以下操作来进一步减少常量:

clusterArr[j]

然后使用clusterArr[k]clusters.get(k)代替{{1}}等。

(名称略微损坏以避免换行)

答案 5 :(得分:1)

http://nlp.stanford.edu/IR-book/information-retrieval-book.html

阅读第6章后,我得到了以下想法
    public class WHN implements Comparable<WHN>{
        private HierarchNode node;
        private float weight;

        public HierarchNode getNode() {return node;}
        public float getWeight() {return weight;}

        public WHN(HierarchNode node, float weight) {this.node = node;this.weight = weight;}

        public int compareTo(WHN o) {return Float.compare(this.weight, o.weight); }
    }

    Map<Tag,<SortedMap<Float,HierarchNode>> map = new HashMap<Tag,List<WHN>> 
    for (HierarchNode n : cluster){
    for (Map.Entry tw : n.tags.entrySet()){
        Tag tag = tw.getKey();
        Float weight = tw.getValue();
        if (!map.ContainsKey(tag)){
            map.put(tag,new ArrayList<WHN>();
        }
        map.get(tag).add(new WHN(n,weight));
    }
    for(List<WHN> l: map.values()){
        Collections.Sort(l);
    }
}

然后为每个节点: 你可以将搜索限制为每个标签具有N个最高权重的元素的并集(称为冠军列表)

或者您可以为每个节点保留一个临时点积并更新每个标记的点积,但只能循环通过权重高于原始节点权重的某个部分的节点(您可以使用Collection.binarySearch找到开始)

我建议你阅读本书的其余部分,因为它可能包含更好的算法。

答案 6 :(得分:0)

上面的代码中没有很多复杂的操作。主要是简单的数字读取/检查/写入。它们非常快。

问题是.get()是一个函数调用 - 与简单+=<=相比,它会慢操作。如果它对你来说太慢了,你应该开始使用真正的数组或(如其他人所说)首先优化你的算法。

答案 7 :(得分:0)

如果你正在迭代这个过程,每次找到下一个最相似的对,你可能会很好地创建一个从i,j对到相似性度量的映射 - 取决于处理器密集度计算相似性的方式,你有多少物品,以及你有多少记忆。

答案 8 :(得分:0)

与算法更改相比,本地优化不会太多。我们不确定你想在第一时间做什么,因此我们无法给你最好/最好的答案。

从我看来,似乎你有很多元素,每个元素都包含一个(标签,重量)列表。所以,这里有一些不清楚的事情:

  • “重量”是从另一个地方计算出来的,还是相应标签的静态?
  • 我们可以使用Integer而不是Float,这样数字计算会更快吗?顺便说一句,您的程序中存在一个错误,即您正在比较浮点数(我的意思是max == 1.0f)。但是,你应该使用&gt;或者&lt;在这种情况下具有精确范围。

如果有“是”,我们会进行一些局部优化。但不,请考虑以下技术(这取决于您的实际数据和实际问题):

  • 排序所有元素会有帮助吗?在某些情况下,这将增加切断计算的机会......
  • 使用动态编程技术,例如逐步更新相关性。您必须在每次更新时迭代所有元素,但以后会节省大量精力。
  • 并行代码(在多个线程上运行),因此它利用了多核环境。如果您担心锁定,请尝试使用lock-free Map/List