包含、存在和任何的性能基准测试

Posted

技术标签:

【中文标题】包含、存在和任何的性能基准测试【英文标题】:Performance Benchmarking of Contains, Exists and Any 【发布时间】:2013-09-10 05:01:42 【问题描述】:

我一直在寻找ContainsExistsAny 方法之间的性能基准测试,这些方法在List<T> 中可用。我只是出于好奇才想知道这一点,因为我总是对这些感到困惑。许多关于 SO 的问题都描述了这些方法的定义,例如:

    LINQ Ring: Any() vs Contains() for Huge Collections Linq .Any VS .Exists - Whats the difference? LINQ extension methods - Any() vs. Where() vs. Exists()

所以我决定自己做。我将其添加为答案。任何对结果的更多见解都是最受欢迎的。我还对数组进行了此基准测试以查看结果

【问题讨论】:

【参考方案1】:

最快的方法是使用HashSetContainsHashSet 是 O(1)。

我获取了您的代码并为HashSet<int> 添加了基准 HashSet<int> set = new HashSet<int>(list); 的性能成本几乎为零。

代码

void Main()

    ContainsExistsAnyVeryShort();
    
    ContainsExistsAnyShort();

    ContainsExistsAny();


private static void ContainsExistsAny()

    Console.WriteLine("***************************************");
    Console.WriteLine("********* ContainsExistsAny ***********");
    Console.WriteLine("***************************************");

    List<int> list = new List<int>(6000000);
    Random random = new Random();
    for (int i = 0; i < 6000000; i++)
    
        list.Add(random.Next(6000000));
    
    int[] arr = list.ToArray();
    HashSet<int> set = new HashSet<int>(list);

    find(list, arr, set, (method, stopwatch) => $"method: stopwatch.ElapsedMillisecondsms");



private static void ContainsExistsAnyShort()

    Console.WriteLine("***************************************");
    Console.WriteLine("***** ContainsExistsAnyShortRange *****");
    Console.WriteLine("***************************************");

    List<int> list = new List<int>(2000);
    Random random = new Random();
    for (int i = 0; i < 2000; i++)
    
        list.Add(random.Next(6000000));
    
    int[] arr = list.ToArray();
    HashSet<int> set = new HashSet<int>(list);

    find(list, arr, set, (method, stopwatch) => $"method: stopwatch.ElapsedMillisecondsms");



private static void ContainsExistsAnyVeryShort()

    Console.WriteLine("*******************************************");
    Console.WriteLine("***** ContainsExistsAnyVeryShortRange *****");
    Console.WriteLine("*******************************************");

    List<int> list = new List<int>(10);
    Random random = new Random();
    for (int i = 0; i < 10; i++)
    
        list.Add(random.Next(6000000));
    
    int[] arr = list.ToArray();
    HashSet<int> set = new HashSet<int>(list);

    find(list, arr, set, (method, stopwatch) => $"method: stopwatch.ElapsedTicks ticks");



private static void find(List<int> list, int[] arr, HashSet<int> set, Func<string, Stopwatch, string> format)

    Random random = new Random();
    int[] find = new int[10000];
    for (int i = 0; i < 10000; i++)
    
        find[i] = random.Next(6000000);
    

    Stopwatch watch = Stopwatch.StartNew();
    for (int rpt = 0; rpt < 10000; rpt++)
    
        list.Contains(find[rpt]);
    
    watch.Stop();
    Console.WriteLine(format("List/Contains", watch));

    watch = Stopwatch.StartNew();
    for (int rpt = 0; rpt < 10000; rpt++)
    
        list.Exists(a => a == find[rpt]);
    
    watch.Stop();
    Console.WriteLine(format("List/Exists", watch));

    watch = Stopwatch.StartNew();
    for (int rpt = 0; rpt < 10000; rpt++)
    
        list.Any(a => a == find[rpt]);
    
    watch.Stop();
    Console.WriteLine(format("List/Any", watch));

    watch = Stopwatch.StartNew();
    for (int rpt = 0; rpt < 10000; rpt++)
    
        arr.Contains(find[rpt]);
    
    watch.Stop();
    Console.WriteLine(format("Array/Contains", watch));

    watch = Stopwatch.StartNew();
    for (int rpt = 0; rpt < 10000; rpt++)
    
        Array.Exists(arr, a => a == find[rpt]);
    
    watch.Stop();
    Console.WriteLine(format("Array/Exists", watch));

    watch = Stopwatch.StartNew();
    for (int rpt = 0; rpt < 10000; rpt++)
    
        Array.IndexOf(arr, find[rpt]);
    
    watch.Stop();
    Console.WriteLine(format("Array/IndexOf", watch));

    watch = Stopwatch.StartNew();
    for (int rpt = 0; rpt < 10000; rpt++)
    
        arr.Any(a => a == find[rpt]);
    
    watch.Stop();
    Console.WriteLine(format("Array/Any", watch));

    watch = Stopwatch.StartNew();
    for (int rpt = 0; rpt < 10000; rpt++)
    
        set.Contains(find[rpt]);
    
    watch.Stop();
    Console.WriteLine(format("HashSet/Contains", watch));

结果

*******************************************
***** ContainsExistsAnyVeryShortRange *****
*******************************************
List/Contains: 1067 ticks
List/Exists: 2884 ticks
List/Any: 10520 ticks
Array/Contains: 1880 ticks
Array/Exists: 5526 ticks
Array/IndexOf: 601 ticks
Array/Any: 13295 ticks
HashSet/Contains: 6629 ticks
***************************************
***** ContainsExistsAnyShortRange *****
***************************************
List/Contains: 4ms
List/Exists: 28ms
List/Any: 138ms
Array/Contains: 6ms
Array/Exists: 34ms
Array/IndexOf: 3ms
Array/Any: 96ms
HashSet/Contains: 0ms
***************************************
********* ContainsExistsAny ***********
***************************************
List/Contains: 11504ms
List/Exists: 57084ms
List/Any: 257659ms
Array/Contains: 11643ms
Array/Exists: 52477ms
Array/IndexOf: 11741ms
Array/Any: 194184ms
HashSet/Contains: 3ms

编辑 (2021-08-25)

我为非常短的集合(10 项)添加了比较,还添加了Array.ContainsArray.IndexOf。您可以看到 Array.IndexOf 对于如此小的范围来说是最快的。也就是说,因为正如@lucky-brian 所说,这里的 n 非常小,所以 for-loop 比稍微复杂的搜索算法执行得更好。但是我仍然建议尽可能使用HashSet&lt;T&gt;,因为它更好地反映了只有唯一值的意图,并且小集合的差异低于 1ms。

【讨论】:

我正在寻找这样的东西。当我看到 HashSet/Contains 上的表现时,我就像“Holy Molly”。肯定会在我的 1000 .. 5000 个项目的环境中尝试一下。 在这里也能看到 HashMap 的性能会很高兴。 你知道 HashSet 的 Any 是否也是 O(1) 吗? Any 在 HashSet&lt;T&gt; 上不存在,但它是 IEnumerable&lt;T&gt; 的扩展方法,并且是 O(1) 没有谓词和 O(n) 有一个谓词。 Herse 是来源:github.com/Microsoft/referencesource/blob/master/System.Core/… "数组不存在" ? Array.Exists(docs.microsoft.com/en-us/dotnet/api/…【参考方案2】:

根据文档:

List.Exists(对象方法)

确定 List(T) 是否包含与 由指定谓词定义的条件。

IEnumerable.Any(扩展方法)

确定序列的任何元素是否满足条件。

List.Contains(对象方法)

确定一个元素是否在列表中。

基准测试:

代码:

    static void Main(string[] args)
    
        ContainsExistsAnyShort();

        ContainsExistsAny();
    
    
    private static void ContainsExistsAny()
    
        Console.WriteLine("***************************************");
        Console.WriteLine("********* ContainsExistsAny ***********");
        Console.WriteLine("***************************************");

        List<int> list = new List<int>(6000000);
        Random random = new Random();
        for (int i = 0; i < 6000000; i++)
        
            list.Add(random.Next(6000000));
        
        int[] arr = list.ToArray();

        find(list, arr);
    

    private static void ContainsExistsAnyShort()
    
        Console.WriteLine("***************************************");
        Console.WriteLine("***** ContainsExistsAnyShortRange *****");
        Console.WriteLine("***************************************");

        List<int> list = new List<int>(2000);
        Random random = new Random();
        for (int i = 0; i < 2000; i++)
        
            list.Add(random.Next(6000000));
        
        int[] arr = list.ToArray();

        find(list, arr);
    

    private static void find(List<int> list, int[] arr)
    
        Random random = new Random();
        int[] find = new int[10000];
        for (int i = 0; i < 10000; i++)
        
            find[i] = random.Next(6000000);
        

        Stopwatch watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 10000; rpt++)
        
            list.Contains(find[rpt]);
        
        watch.Stop();
        Console.WriteLine("List/Contains: 0:N0ms", watch.ElapsedMilliseconds);

        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 10000; rpt++)
        
            list.Exists(a => a == find[rpt]);
        
        watch.Stop();
        Console.WriteLine("List/Exists: 0:N0ms", watch.ElapsedMilliseconds);

        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 10000; rpt++)
        
            list.Any(a => a == find[rpt]);
        
        watch.Stop();
        Console.WriteLine("List/Any: 0:N0ms", watch.ElapsedMilliseconds);

        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 10000; rpt++)
        
            arr.Contains(find[rpt]);
        
        watch.Stop();
        Console.WriteLine("Array/Contains: 0:N0ms", watch.ElapsedMilliseconds);

        Console.WriteLine("Arrays do not have Exists");

        watch = Stopwatch.StartNew();
        for (int rpt = 0; rpt < 10000; rpt++)
        
            arr.Any(a => a == find[rpt]);
        
        watch.Stop();
        Console.WriteLine("Array/Any: 0:N0ms", watch.ElapsedMilliseconds);
    

结果

***************************************
***** ContainsExistsAnyShortRange *****
***************************************
List/Contains: 96ms
List/Exists: 146ms
List/Any: 381ms
Array/Contains: 34ms
Arrays do not have Exists
Array/Any: 410ms
***************************************
********* ContainsExistsAny ***********
***************************************
List/Contains: 257,996ms
List/Exists: 379,951ms
List/Any: 884,853ms
Array/Contains: 72,486ms
Arrays do not have Exists
Array/Any: 1,013,303ms

【讨论】:

请记住,尽管 Contains 似乎是最快的,但 LINQ 2 SQL 在列表中限制为 ~2100 个对象,因此它适用于较短的列表。 @jyparask 即使对于较大的列表,包含似乎也不错。但是,我也更新了较短列表的代码和时间。结果如你所料。 我运行了你的基准测试,我发现 List.Exists 实际上比 List.Contains 略快,分别为 45 毫秒和 55 毫秒。其余的似乎与您的结果一致。在 .NET 4.5 上使用 Visual Studio 2013 在 32 位发布模式下进行了优化测试。 你是什么意思:“数组不存在”?我认为它有 Exists():***.com/a/22928748/4608491 "数组不存在" ? Array.Exists(docs.microsoft.com/en-us/dotnet/api/…【参考方案3】:

值得一提的是,这种比较有点不公平,因为Array 类不拥有Contains() 方法。它通过连续的Enumerator 使用IEnumerable&lt;T&gt; 的扩展方法,因此它没有针对Array 实例进行优化。另一方面,HashSet&lt;T&gt; 有自己的实现,针对所有大小进行了全面优化。

为了公平比较,您可以使用为Array 实例实现的静态方法int Array.IndexOf(),尽管它使用的for 循环比Enumerator 更有效。

使用公平的比较算法,HashSet&lt;T&gt;.Contains() 的最多 5 个元素的小集合的性能与 Array.IndexOf() 相似,但对于更大的集合更有效。

【讨论】:

以上是关于包含、存在和任何的性能基准测试的主要内容,如果未能解决你的问题,请参考以下文章

PCMark 10( PC基准性能测试工具 )中文版分享

MATLAB 性能基准测试

公司HBase基准性能测试之准备篇

如何测量网络性能(如何对网络协议进行基准测试)

#yyds干货盘点#Go 语言入门很简单:基准测试

Python 系统的基准测试系统性能