【问题标题】:how to speed up linq query on csv imported list如何加快对csv导入列表的linq查询
【发布时间】:2019-05-02 13:01:03
【问题描述】:

我的任务是匹配 170 万条记录和一些结果,这些结果以 csv 文件的形式传递给我。

以下代码的一些背景知识,我有两个列表...

包含 5 个属性的证书,其 ID 相当于一个 PK。

包含应包含在证书列表中的 ID 列表的订单。

我需要匹配这两者并对找到的那些 Cert 对象做一些事情。

            foreach (Classes.CertOrder.IDS OrderUnitID in Order.AllIDs)
            {
                var Cert = (from C in Certs where C.ID.ToUpper() == OrderUnitID.ID.ToUpper() select C).FirstOrDefault();
                if (Cert != null)
                {
                    Output.add(Cert)
                    OrderUnitID.fulfilled = true;
                }

            }

这段代码可以工作,但速度超级慢(我猜是记录的数量)有什么办法可以加快速度吗?

编辑到添加,希望能够将数据添加到 SQL 服务器以运行查询,但是数据不允许离开正在处理文件的工作站,甚至不允许触摸未加密形式的磁盘。

结合下面的有用答案,我将输出更改为基于列表,按 ID 对两个列表进行预排序,现在处理需要几秒钟而不是几小时!感谢堆栈溢出!

【问题讨论】:

  • 有什么办法可以加快速度 是的,很多。哪个是最好的,你将不得不做一些研究。查看Task.Run() 可能是一种选择,或者Parrellel.ForEach,这是否会“更快”,也许,您必须对其进行测试。当然,如果您将此 CSV 加载到 SQL 之类的东西中,它可以更快地处理这些数据。 Dictionary 可能会加快查找速度,但您必须权衡最初创建字典的开销。基本上这太宽泛了
  • 如果您希望没有那么多匹配项,则缓存所有 Cert id,对 id 进行搜索,如果您有 match,则进行查询以按 id 提取 Cert 对象。
  • 在 Certs 中应该只有一个匹配 order.allids 但是 Certs 列表包含许多订单的结果
  • Certs 中是否有重复项?我的意思是,具有相同ID的项目?
  • 两个 ID 列表中都没有重复项。

标签: c# linq csv large-data


【解决方案1】:

在已接受的答案上扩展了一些其他选项。 OrdinalIgnoreCase 为您提供最佳的单线程性能,而并行化它提供最佳的整体性能。

class Item { public string Id { get; set; } }

class Program
{
    private static Random rng = new Random();
    private static string characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
    static void Main(string[] args)
    {
        var list = Enumerable.Range(1, 2_700_000)
            .Select(x => string.Join("", Enumerable.Range(5, rng.Next(20)).Select(y => characters[rng.Next(0, characters.Length)])))
            .Distinct(StringComparer.OrdinalIgnoreCase)
            .Select(x => new {Order = rng.Next(), Item = new Item {Id = x }})
            .OrderBy(x => x.Order)
            .Select(x => x.Item)
            .ToList();

        Console.WriteLine("Master List Size: {0}", list.Count);

        var matches = list.Take(350_000).Select(x => x.Id).ToList();

        Console.WriteLine("Matches List Size: {0}", matches.Count);

        var dict = list.ToDictionary(x => x.Id, x => x, StringComparer.CurrentCultureIgnoreCase);

        var results = new List<Item>();

        var sw = new Stopwatch();

        Console.WriteLine("CurrentCultureIgnoreCase Elapsed Time (avg): {0}",
            Enumerable.Range(1, 10).Select(x =>
            {
                sw.Start();

                foreach (var m in matches)
                    if (dict.TryGetValue(m, out var item))
                        results.Add(item);

                sw.Stop();

                var t = sw.ElapsedMilliseconds;

                sw.Reset();

                return t;
            }).Average());


        dict = list.ToDictionary(x => x.Id.ToUpper(), x => x);

        Console.WriteLine("ToUpper() Elapsed Time (avg): {0}",
            Enumerable.Range(1, 10).Select(x =>
            {
                sw.Start();

                foreach (var m in matches)
                    if (dict.TryGetValue(m.ToUpper(), out var item))
                        results.Add(item);

                sw.Stop();

                var t = sw.ElapsedMilliseconds;

                sw.Reset();

                return t;
            }).Average());


        dict = list.ToDictionary(x => x.Id, x => x, StringComparer.OrdinalIgnoreCase);

        Console.WriteLine("OrdinalIgnoreCase Elapsed Time (avg): {0}",
            Enumerable.Range(1, 10).Select(x =>
            {
                sw.Start();

                foreach (var m in matches)
                    if (dict.TryGetValue(m, out var item))
                        results.Add(item);

                sw.Stop();

                var t = sw.ElapsedMilliseconds;

                sw.Reset();

                return t;
            }).Average());
    }
}

var cDict = new ConcurrentDictionary<string,Item>(dict);
var cResults = new ConcurrentBag<Item>();

Console.WriteLine("Parallel Elapsed Time (avg): {0}",
    Enumerable.Range(1, 10).Select(x =>
    {
        sw.Start();

        Parallel.ForEach(matches, new ParallelOptions{MaxDegreeOfParallelism = 20}, m =>
        {
            if (cDict.TryGetValue(m, out var item))
                cResults.Add(item);
        });
        sw.Stop();

        var t = sw.ElapsedMilliseconds;

        sw.Reset();

        return t;
    }).Average());

结果

 Master List Size: 2158882
 Matches List Size: 350000
 CurrentCultureIgnoreCase Elapsed Time (avg): 298.2
 ToUpper() Elapsed Time (avg): 179.6
 OrdinalIgnoreCase Elapsed Time (avg): 163.9
 Parallel Elapsed Time (avg): 74.6

【讨论】:

    【解决方案2】:

    Certs构建字典:

    var certsMapping = Certs
        .ToDictionary(_ => _.ID.ToUpper());
    
    foreach (Classes.CertOrder.IDS OrderUnitID in Order.AllIDs)
    {
        if (certMapping.TryGetValue(OrderUnitID.ID.ToUpper(), out var cert))
        {
            Output.add(cert);
            OrderUnitID.fulfilled = true;
        }
    }
    

    【讨论】:

    • 谢谢丹尼斯,这本词典确实有很大帮助,我现在每分钟处理数千个而不是数百个。完成 170 万条记录仍然需要一段时间,但至少现在它会在我退休前完成。
    • 哪个列表有 170 万条记录?请张贴这两种尺寸的列表。
    • Certs 包含 170 万个,所有订单 ID 包含 90k 到 350k 个 ID,具体取决于正在处理的订单对象。从证书列表中删除证书的额外处理是否值得花时间,例如当证书被匹配和处理时,将其从列表中删除,因为它不会被再次使用或需要。
    • 嗯。字典查找工作非常快。无需从字典中删除项目 - 它不会执行得更快。 Output 是什么?设置OrderUnitID.fulfilled 是否只是设置bool(没有副作用)?我的意思是,只需遍历List&lt;T&gt;(如果Order.AllIDs 是一个列表),字典查找应该不会花费太多时间。
    • 抱歉 output.add 只是示例行,实际行包含安全敏感信息。它基本上将证书对象的一些属性和一个主密钥添加到一个字符串,该字符串稍后将输出到 csv。 Order.AllIDs 是 IDS 对象的列表,其中包含作为字符串的 ID 和已完成的布尔值。
    【解决方案3】:

    为什么数据库查找更快? 原因之一是索引

    您可以使用i4o 为内存列表创建索引。

    然后使用并行的 for-each 循环来加快速度。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-08-23
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-03-25
      • 2023-03-26
      • 2016-03-01
      • 1970-01-01
      相关资源
      最近更新 更多