Java性能:removeAll()上的搜索和删除速度
我比较了Java性能:removeAll()上的搜索和删除速度,java,performance,collections,benchmarking,Java,Performance,Collections,Benchmarking,我比较了removeAll(Collection c)调用在Collection中声明的速度,觉得很有趣。现在我知道微基准测试很难做对,我不会看几毫秒的差异,但我相信我的结果是有效的,因为我反复运行它们,它们是非常可复制的 让我们假设有两个集合不是太小,比如100000个连续整数元素,而且它们大部分重叠,例如5000个在左侧,但不在右侧。现在我简单地说: left.removeAll(right); 当然,这一切都取决于左集合和右集合的类型。如果正确的集合是一个哈希映射,那么它的速度会非常快,
removeAll(Collection c)
调用在Collection
中声明的速度,觉得很有趣。现在我知道微基准测试很难做对,我不会看几毫秒的差异,但我相信我的结果是有效的,因为我反复运行它们,它们是非常可复制的
让我们假设有两个集合不是太小,比如100000个连续整数元素,而且它们大部分重叠,例如5000个在左侧,但不在右侧。现在我简单地说:
left.removeAll(right);
当然,这一切都取决于左集合和右集合的类型。如果正确的集合是一个哈希映射,那么它的速度会非常快,因为这就是查找的地方。但仔细观察,我发现了两个我无法解释的结果。我用排序的ArrayList
和另一个被洗牌的Collections.shuffle()
尝试了所有的测试
第一个奇怪的结果是:
00293 025% shuffled ArrayList, HashSet
00090 008% sorted ArrayList, HashSet
现在,从排序的ArrayList
中删除元素比从无序列表中删除要快,或者从HashSet
中查找连续值比查找随机值要快
另一个:
02311 011% sorted ArrayList, shuffled ArrayList
01401 006% sorted ArrayList, sorted ArrayList
现在,这表明排序后的ArrayList
(对左侧列表的每个元素使用contains()
调用)中的查找速度比无序列表中的快。现在,如果我们能利用它被排序的事实并使用二进制搜索,这将是非常容易的,但我不这样做
这两个结果对我来说都很神秘。我无法通过查看代码或使用我的数据结构知识来解释它们。它是否与处理器缓存访问模式有关?JIT编译器在优化东西吗?但如果是,那是什么?我进行了热身并连续运行了几次测试,但我的基准测试可能有一个根本性的问题 现在我知道微基准测试很难做对,我不会看几毫秒的差异,但我相信我的结果是有效的,因为我反复运行它们,它们是非常可复制的 这不能说服我。有缺陷基准的行为可以100%重复
我怀疑。。。事实上基准测试中的一个或多个缺陷>>正在查看(OpenJDK7-b147)的源代码。它似乎委托给名为
batchRemove()
的私有方法,如下所示:
663 private boolean batchRemove(Collection<?> c, boolean complement) {
664 final Object[] elementData = this.elementData;
665 int r = 0, w = 0;
666 boolean modified = false;
667 try {
668 for (; r < size; r++)
669 if (c.contains(elementData[r]) == complement)
670 elementData[w++] = elementData[r];
671 } finally {
672 // Preserve behavioral compatibility with AbstractCollection,
673 // even if c.contains() throws.
674 if (r != size) {
675 System.arraycopy(elementData, r,
676 elementData, w,
677 size - r);
678 w += size - r;
679 }
680 if (w != size) {
681 for (int i = w; i < size; i++)
682 elementData[i] = null;
683 modCount += size - w;
684 size = w;
685 modified = true;
686 }
687 }
688 return modified;
689 }
(method) (numElements) Mode Cnt Score Error Units
sorted 1000 avgt 50 52,055 ± 0,507 us/op
shuffled 1000 avgt 50 55,720 ± 0,466 us/op
sorted 10000 avgt 50 5341,917 ± 28,630 us/op
shuffled 10000 avgt 50 7108,845 ± 45,869 us/op
sorted 100000 avgt 50 621714,569 ± 19040,964 us/op
shuffled 100000 avgt 50 1110301,876 ± 22935,976 us/op
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Random;
import java.util.function.Function;
public class RemoveAllBenchmarkExt
{
public static void main(String[] args)
{
for (int n=10000; n<=100000; n+=10000)
{
runTest(n, sortedList() , sortedList());
runTest(n, sortedList() , shuffledList(0.00));
runTest(n, sortedList() , shuffledList(0.25));
runTest(n, sortedList() , shuffledList(0.50));
runTest(n, sortedList() , shuffledList(0.75));
runTest(n, sortedList() , shuffledList(1.00));
runTest(n, sortedList() , reversedList());
System.out.println();
}
}
private static Function<Integer, Collection<Integer>> sortedList()
{
return new Function<Integer, Collection<Integer>>()
{
@Override
public Collection<Integer> apply(Integer t)
{
List<Integer> list = new ArrayList<Integer>(t);
for (int i=0; i<t; i++)
{
list.add(i);
}
return list;
}
@Override
public String toString()
{
return "sorted";
}
};
}
private static Function<Integer, Collection<Integer>> shuffledList(
final double degree)
{
return new Function<Integer, Collection<Integer>>()
{
@Override
public Collection<Integer> apply(Integer t)
{
List<Integer> list = new ArrayList<Integer>(t);
for (int i=0; i<t; i++)
{
list.add(i);
}
shuffle(list, degree);
return list;
}
@Override
public String toString()
{
return String.format("shuffled(%4.2f)", degree);
}
};
}
private static void shuffle(List<Integer> list, double degree)
{
Random random = new Random(0);
int n = (int)(degree * list.size());
for (int i=n; i>1; i--)
{
swap(list, i-1, random.nextInt(i));
}
}
private static void swap(List<Integer> list, int i, int j)
{
list.set(i, list.set(j, list.get(i)));
}
private static Function<Integer, Collection<Integer>> reversedList()
{
return new Function<Integer, Collection<Integer>>()
{
@Override
public Collection<Integer> apply(Integer t)
{
List<Integer> list = new ArrayList<Integer>(t);
for (int i=0; i<t; i++)
{
list.add(i);
}
Collections.reverse(list);
return list;
}
@Override
public String toString()
{
return "reversed";
}
};
}
private static void runTest(int n,
Function<Integer, ? extends Collection<Integer>> leftFunction,
Function<Integer, ? extends Collection<Integer>> rightFunction)
{
Collection<Integer> left = leftFunction.apply(n);
Collection<Integer> right = rightFunction.apply((int)(n*0.95));
long before = System.nanoTime();
left.removeAll(right);
long after = System.nanoTime();
double durationMs = (after - before) / 1e6;
System.out.printf(
"%8d elements, %15s, duration %10.3f ms, size %d\n",
n, rightFunction, durationMs, left.size());
}
}
100000 elements, sorted, duration 6016,354 ms, size 5000
100000 elements, shuffled(0,00), duration 5849,537 ms, size 5000
100000 elements, shuffled(0,25), duration 7319,948 ms, size 5000
100000 elements, shuffled(0,50), duration 9344,408 ms, size 5000
100000 elements, shuffled(0,75), duration 10657,021 ms, size 5000
100000 elements, shuffled(1,00), duration 11295,808 ms, size 5000
100000 elements, reversed, duration 5830,695 ms, size 5000
663私有布尔批处理删除(集合c,布尔补码){
664最终对象[]elementData=this.elementData;
665 int r=0,w=0;
666布尔修改=假;
667试试看{
668用于(;r
它实际上在数组中循环,并有一系列c.contains()
调用。基本上,对于排序的数组,没有理由让迭代速度更快
我支持StephenC对基准测试的质疑,并相信在深入研究缓存访问模式等之前仔细检查基准测试代码会更有成效
此外,如果基准代码不是罪魁祸首,那么了解java版本和OS/arch等也很有意思。性能差异的原因是内存访问模式:访问内存中连续的元素比随机内存访问要快(由于内存预取、cpu缓存等原因) 最初填充集合时,会在内存中按顺序创建所有元素,因此在遍历集合(foreach、removeAll等)时,会访问连续的内存区域,这是缓存友好的。当您洗牌集合时-元素在内存中保持相同的顺序,但指向这些元素的指针不再具有相同的顺序,因此当您遍历集合时,您将访问例如第10个、第1个、然后是第5个元素,这非常不利于缓存并破坏性能 您可以更详细地查看此问题,其中可以看到此效果:
由于询问者没有提供任何示例代码,并且对评论和回答中提到的基准有疑问,我创建了一个小测试,以查看当参数是无序列表(而不是排序列表)时,
removeAll
方法是否较慢。我确认了提问者的观察结果:测试的输出大致是
100000 elements, sortedList and sortedList, 5023,090 ms, size 5000
100000 elements, shuffledList and sortedList, 5062,293 ms, size 5000
100000 elements, sortedList and shuffledList, 10657,438 ms, size 5000
100000 elements, shuffledList and shuffledList, 10700,145 ms, size 5000
我将在这里省略这个特定测试的代码,因为它也受到了质疑(顺便说一句,这是完全合理的!很多BS发布在web上…)
所以我做了进一步的测试,我将在这里提供代码
这也可能不是一个明确的答案。但我试图调整测试,以便它们至少提供一些强有力的证据,证明性能降低的原因确实是什么(+1,如果它能说服您,请接受)。也就是说,减速的原因在于
100000 elements, sorted, duration 6016,354 ms, size 5000
100000 elements, shuffled(0,00), duration 5849,537 ms, size 5000
100000 elements, shuffled(0,25), duration 7319,948 ms, size 5000
100000 elements, shuffled(0,50), duration 9344,408 ms, size 5000
100000 elements, shuffled(0,75), duration 10657,021 ms, size 5000
100000 elements, shuffled(1,00), duration 11295,808 ms, size 5000
100000 elements, reversed, duration 5830,695 ms, size 5000