【问题标题】:Why C# linq Distinct method is faster为什么 C# linq Distinct 方法更快
【发布时间】:2020-06-20 16:19:39
【问题描述】:

我已经检查过任何嵌套循环的不同性能。但是 Distinct Method 比嵌套循环快得多。

var customers = new List<Customer>();

            for (var i = 1; i <= 100000; i++)
            {
                var id = (int)Math.Floor((decimal)i / 10);
                var customer = new Customer()
                {
                    FirstName = $"Name {i}",
                    ID = id,
                    LastName = $"Last {i}"
                };

                customers.Add(customer);
            }

            System.Console.WriteLine($"Outer Loop start :{DateTime.UtcNow}");

            var ids = new List<int>();

            customers.ForEach(_=> {
                ids.Add(_.ID);
            });

            var uniqueIds = ids.Distinct();

            System.Console.WriteLine($"Outer Loop End :{DateTime.UtcNow}");

            System.Console.WriteLine($"Nested Loop start :{DateTime.UtcNow}");

            var oids = new List<int>();

            customers.ForEach(_ => {
                if (!oids.Any(i => i == _.ID))
                {
                    oids.Add(_.ID);
                }
            });
            System.Console.WriteLine($"Nested Loop End :{DateTime.UtcNow}");

结果: 外环开始:2020 年 6 月 20 日下午 4:15:31 外环结束:2020 年 6 月 20 日下午 4:15:31 嵌套循环开始:2020 年 6 月 20 日下午 4:15:32 嵌套循环结束:2020 年 6 月 20 日下午 4:15:46

Outerloop 只用了 1 秒,而嵌套循环只用了 14 秒。 Distinct 比在 foreach 中使用“Any”函数快得多?

【问题讨论】:

  • 我只是想找出客户列表中的唯一 ID。
  • 循环是一样的。不同的是,我认为使用散列来获取键,这对于大量项目列表来说更快。因此,您可能正在搜索性能为 N/2 的列表,而 distinct 使用的是 log2(N) 的哈希。
  • 此外,Stopwatch 类对于测量执行时间更准确。请参阅:stackoverflow.com/questions/2923283/…
  • 仅供参考,没有显示“内循环”,只有一个循环中的 if 语句。
  • 是的@RufusL,我在那个 if 语句中使用 Any 方法,我认为这是一个嵌套循环。

标签: c# performance linq optimization


【解决方案1】:

首先它更快,因为 Distinct 实际上几乎什么都不做 - uniqueIds 没有实现 IEnumerable&lt;int&gt;(您可以检查它在 ids.Distinct() 之间添加 .Select(c =&gt; {Console.WriteLine(c);return c;}) 例如),更改 @ 987654333@声明行至:

var uniqueIds = ids.Distinct().ToList();

对于适当的基准测试,我建议使用BenchmarkDotNet,对于您的情况,您可以编写例如以下基准测试(删除/重新组织了一些代码,因为它与实际的基准测试内容无关):

public class GetDistinctIds
{
    private static readonly List<int> CustomerIds = Enumerable.Range(0, 100_000)
       .Select(i => (int) Math.Floor((decimal) i / 10))
       .ToList();

    [Benchmark]
    public List<int> Distinct() => CustomerIds.Distinct().ToList();

    [Benchmark]
    // just for fun =)
    // returning object so BenchmarkDotNet won't complain, actually non-materialized IEnumerable<int>
    public object DistinctNoToList() => CustomerIds.Distinct();

    [Benchmark]
    public List<int> HashSet() => new HashSet<int>(CustomerIds).ToList();

    [Benchmark]
    public List<int> NestedLoops()
    {
        var oids = new List<int>();

        CustomerIds.ForEach(id =>
        {
            if (!oids.Any(i => i == id))
            {
                oids.Add(id);
            }
        });
        return oids;
    }
}

在我的机器上给出下一个结果:

|           Method |                Mean |             Error |            StdDev |
|----------------- |--------------------:|------------------:|------------------:|
|         Distinct |     1,842,519.98 ns |     16,088.362 ns |     17,882.171 ns |
| DistinctNoToList |            17.19 ns |          0.412 ns |          1.070 ns |
|          HashSet |     1,911,107.12 ns |     31,699.290 ns |     29,651.535 ns |
|      NestedLoops | 4,100,604,547.06 ns | 78,815,290.539 ns | 80,937,500.636 ns |

最后是“为什么”

Distinct 在内部使用DistinctIterator,而后者又使用内部Set 类,描述为A lightweight hash set,据我了解,在搜索Big-O 复杂性方面应该与hashtable 相当,结果constant search time 在最佳/平均情况下,而 List 将具有 O(n) 的搜索(!oids.Any)复杂度。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-08-03
    • 1970-01-01
    • 1970-01-01
    • 2013-06-13
    • 2020-11-26
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多