【问题标题】:Fast intersection of HashSet<int> and List<int>HashSet<int> 和 List<int> 的快速交集
【发布时间】:2020-08-16 05:34:16
【问题描述】:

我有一个HashSet&lt;int&gt; 和一个List&lt;int&gt;(Hashset 有大约 300 万个项目,List 有大约 300k 个项目)。

我目前使用

与它们相交
var intersected = hashset.Intersect(list).ToArray();

我想知道是否有更快的方法可以做到这一点。也许并行?

【问题讨论】:

  • 需要HashSet和List吗?

标签: c# algorithm performance intersection hashset


【解决方案1】:

HashSet 有一个方法IntersectWith,即optimized if intersection is performed between two hash sets。使用IntersectWith 方法,我们可以使用下一种方法将HashSetList 相交:

private static IEnumerable<int> Intersect(HashSet<int> hash, List<int> list)
{
    HashSet<int> intersect = new HashSet<int>(list);
    intersect.IntersectWith(hash);
    return intersect;
}

我已经测量了(使用 Stopwatch)您的原始方法 (Linq Intersect)、@TheodorZoulias (HashSet Contains and HashSet Contains Parallel) 和我的方法 (HashSet IntersectWith) 提出的方法的性能。以下是结果:

------------------------------------------------------------------------
|         Method            | Min, ms | Max, ms | Avg, ms | StdDev, ms |
------------------------------------------------------------------------
| Linq Intersect            |   135   |   274   |   150   |     17     |
| HashSet Contains          |    25   |    44   |    26   |      2     |
| HashSet Contains Parallel |    12   |    53   |    13   |      3     |
| HashSet IntersectWith     |    57   |    89   |    61   |      4     |
------------------------------------------------------------------------

从表中可以看出,最快的方法是HashSet Contains Parallel,最慢的是Linq Intersect


这是用于衡量性能的complete source code

【讨论】:

    【解决方案2】:

    是的,您可以走得更快,因为您已经有了HashSet。 LINQ Intersect 使用 a generic algorithm,这实际上是在每次调用时从头开始重新创建 HashSet。这是一个更快的算法:

    /// <summary>Yields all the elements of first (including duplicates) that also
    /// appear in second, in the order in which they appear in first.</summary>
    public static IEnumerable<TSource> Intersect<TSource>(IEnumerable<TSource> first,
        HashSet<TSource> second)
    {
        foreach (TSource element in first)
        {
            if (second.Contains(element)) yield return element;
        }
    }
    

    更新:这是上述想法的并行版本:

    var intersected = list.AsParallel().Where(x => hashset.Contains(x)).ToArray();
    

    我不希望它更快,如果有的话,因为工作量是too granular。调用 lambda 300,000 次的开销可能会掩盖并行性的任何好处。

    同样不会保留结果的顺序,除非在查询中添加AsOrdered PLINQ 方法,进一步损害操作的性能。

    【讨论】:

      【解决方案3】:

      将大量整数存储为紧凑位集而不是HashSetList 可能会更快(至少如果您使用List 来存储唯一整数,就像HashSet )。从这个意义上说,有几种选择:

      • 内置的BitArray 以紧凑的方式存储每个位。例如,如果您要存储从 1 到 65000 的整数,BitArray 需要大约 8125 字节的内存(如果每个位存储为 8 位字节,则需要 65000 字节)。但是,如果最高设置位非常大(例如,30 亿),或者如果位集稀疏(有设置位和/或清除位的巨大区域),BitArray 可能不会非常节省内存。您可以使用Xor 方法将两个BitArrays 相交
      • 压缩位集同样以紧凑的方式存储每个位,但也会压缩自身的一部分以进一步节省内存,同时仍保持集合操作(​​如交集)的效率。示例包括 Elias-Fano 编码、Roaring Bitmaps 和 EWAH。请参阅 graphs 在性能和内存方面比较压缩位集与未压缩 (FixedBitSet) 的不同实现(请注意,它们比较 Java 实现,但它们在 .NET 案例中可能仍然有用)。

      【讨论】:

        猜你喜欢
        • 2022-06-29
        • 1970-01-01
        • 2013-02-17
        • 2019-06-27
        • 2020-08-10
        • 2010-12-08
        • 1970-01-01
        • 2020-03-05
        • 1970-01-01
        相关资源
        最近更新 更多