标准实现B.Intersect(A).Count()
具有简短易读的巨大优势,除非您有衡量的性能问题,否则您应该使用它。
当性能是一个您可以引入的问题时HashSet<int>
,它是资源使用和搜索时间的一个很好的折衷方案。但是,因为您担心性能,我们应该进行一些测试(我正在使用我编写的这个免费工具):
CPU:1.8 GHz Pentium Core 2 Duo
迭代次数:100
每个列表中的项目数:1000
A.Where(a => B.Contains(a)).Count()
: 8338 滴答
A.Intersect(B).Count()
: 288 滴答
B.Count - B.Except(A).Count()
: 313 滴答
现在让我们HashSet<int>
在我们的测试中介绍(从任何其他答案中选择实现):
HashSet<int>
: 163 滴答声
它的表现要好得多。我们能做得更好吗?如果输入范围是已知的(并且是有限的),那么使用BitArray
. 在这个例子中,我假设(为简单起见)只有正数,但很容易适应。
public static int UseBitArray(int range, List<int> listA, List<int> listB) {
var BitArray array = new BitArray(range);
for (int i = 0; i < listA.Count; ++i)
array[listA[i]] = true;
int count = 0;
for (int i = 0; i < listB.Count; ++i) {
if (array[listB[i]])
++count;
}
return count;
}
它表现如何?
BitArray
: 95 滴答
它只需要第二最佳方法的 58% ( HashSet<int>
)。我什至不和别人比较。请注意,它大量使用内存并且在很宽的范围内(比方说Int32.MaxValue / 2
)它使用大量内存(此外,它的大小仅限于Int32.MaxValue
那么你不能拥有完整的有符号 32 位整数范围。如果它的限制不是问题那么你绝对应该接受它。
另请注意,如果您可以对输入进行一些假设,那么您可以进一步优化您的搜索功能(例如,如果您可以假设集合是有序的)。
它们如何按比例放大(Y 轴比例为对数):
请注意,这比项目数量增加时Except
表现更好。Intersect
另请注意,对于这种微不足道的对象(整数),并行执行它不会有任何性能提升(另请参阅查找两个字符串列表之间的差异):比较是如此微不足道,以至于开销和同步化高于收益(除非它是对大量项目进行良好调整的算法)。
如果您真的在寻找最后一点性能提升,您甚至可以实现自己的BitArray
类(没有不需要的东西和错误检查):
sealed class FastBitArray {
public FastBitArray(int length) {
m_array = new int[((length - 1) / 32) + 1];
}
public bool this[int index] {
get {
return (m_array[index / 32] & (1 << (index % 32))) != 0;
}
set {
if (value)
m_array[index / 32] |= (1 << (index % 32));
else
m_array[index / 32] &= ~(1 << (index % 32));
}
}
private int[] m_array;
}
请注意,在 setter 内部有一个分支,我们不必担心将其优化掉,因为true
分支预测器的模式很容易(总是)。没有性能提升使它比这更复杂。
最新测试:
迭代次数:100
每个列表中的项目数:1000000
HashSet<int>
: 144748 滴答
BitArray
: 37292 滴答
FastBitArray
: 28966 滴答
让我们从视觉上比较它们(蓝色系列是测试 1,000 个项目,橙色系列是 1,000,000 个;Y 轴是对数,以便与 1k 系列进行比较)。我们知道很慢的方法被简单地省略了:
相同的数据仅显示 1M 系列和线性 Y 轴: