Java:ArrayList瓶颈
在分析一个计算数千个元素的分层集群的java应用程序时,我意识到Java:ArrayList瓶颈,java,optimization,arraylist,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 u
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
支持
否则,我应该尝试其他更优化的集合库吗?就像谷歌的,或者apache的
编辑:
你多少证实了我的怀疑:(
尝试并行化时,我的性能会得到提升吗?可能会使用一个执行器池来计算多对执行器的相似性……但我不知道数据结构上的同步和锁定是否最终会降低性能
使用两个内容的标记映射的点积计算相似度。映射是两个HashMap
。此外,我已经将相似度缓存在TLongFloatHashMap
(来自Trove集合)为了避免在以后的迭代中重新计算它,Long
键被计算为两个内容的hashcode(这对于这对内容是唯一的,因此hash(c1,c2)==hash(c2,c1)
),所以其他所有内容都已经进行了充分的调优
EDIT2:
为了让您更好地理解,我将发布一点代码。这用于计算用于存储两个元素之间相似性的哈希:
private long computeKey(int h1, int h2) {
if (h1 < h2) {
int swap = h1;
h1 = h2;
h2 = swap;
}
return ((long)h1) << 32 | h2;
}
private long computeKey(inth1,inth2){
if(h1
我本来只会使用一个矩阵来存储所有值,但在每次迭代中,最相似的项都会从列表中删除,并添加一个新项(根据所选的两个项有一个新的标记映射)您使用的算法是O(n²)。除非你有办法让你的算法比两两比较做得更好,否则性能不太可能有明显的提高-(冒着陈述显而易见的风险,使用以下伪代码可能会加快速度:
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]最相似,元素[1]与元素[0]或元素[2]最相似。在这种情况下,可以获得高达O(n lg n)
的速度
EDIT2:考虑到你的相关代码,你的基准测试结果是非常可疑的。我无法想象这两种情况比调用相关代码(即使假设缓存在绝大多数时间都被命中)花费更多的时间,这也被称为
O(n²)
次。如果get()是瓶颈,那么spong在将它们转换为数组方面也做得很好。上面的代码中没有太多复杂的操作。主要是简单的数字读取/检查/写入。它们速度惊人
问题是
.get()是
是一个函数调用-与简单的+
,=
或ArrayList.get相比,它将慢得多。=
或ArrayList.get是一个if语句,后跟一个数组访问。没有太多的优化。ArrayList.get占用了一半的执行时间,因为您没有做任何其他事情。tim中的重要因素e take是迭代次数,而不是for循环中的迭代次数。没有O(n*(n+1)/2)。您的算法是O(n2)。有关更详细的解释,请参阅
Ben是正确的:通过将第i个元素置于内部循环之外,可以减少get()
调用
你真正想要的是在O(n2)的基础上改进的东西,这需要能够对元素做出额外的假设,这取决于你所说的“相似性”
两种常见的方法:
- 对列表进行排序并合并。总的来说,这是O(n log n)
- 将一个列表放入具有(近似)常量查找的
Map
中。这可以根据Map
的类型和遍历的性质将算法减少到O(n)和O(n log n)之间的任意位置
但这一切都取决于你所说的“相似性”是什么意思.如果你在重复这个过程,每次都找到下一个最相似的对,你最好创建一个从i,j对到相似性度量的映射-这取决于计算相似性的处理器密集程度,以及你有多少项,以及你有多少内存。除了算法效率之外,你正在调用get
次数太多。当前调用get
(按)2*size*size的顺序)<
for (int j = 0; j < clusters.size(); ++j) {
skip = false;
for (int k = j+1; k < clusters.size(); ++k) {
float r = correlation(clusters.get(k).tags, clusters.get(j).tags, clusters.get(k), clusters.get(j));
if (r > max) {
max = r;
i1 = j;
i2 = k;
}
if (max == 1.0f) {
skip = true;
break;
}
}
if (skip)
break;
}
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
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 ...
HierarchNode[] clusterArr = clusters.toArray(new HierarchNode[clusters.size()]);
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);
}
}