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

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

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

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

【问题讨论】:

    标签: c# performance benchmarking


    【解决方案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.ElapsedMilliseconds}ms");
    
    }
    
    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.ElapsedMilliseconds}ms");
    
    }
    
    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:N0}ms", 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:N0}ms", 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:N0}ms", watch.ElapsedMilliseconds);
    
            watch = Stopwatch.StartNew();
            for (int rpt = 0; rpt < 10000; rpt++)
            {
                arr.Contains(find[rpt]);
            }
            watch.Stop();
            Console.WriteLine("Array/Contains: {0:N0}ms", 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:N0}ms", 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():stackoverflow.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() 相似,但对于更大的集合更有效。

    【讨论】:

      猜你喜欢
      • 2017-05-29
      • 2014-07-30
      • 2014-12-19
      • 2020-09-27
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-03
      • 1970-01-01
      相关资源
      最近更新 更多