【问题标题】:C# - Linq optimize code with List and Where clauseC# - Linq 使用 List 和 Where 子句优化代码
【发布时间】:2018-05-04 08:35:26
【问题描述】:

我有以下代码:

        var tempResults = new Dictionary<Record, List<Record>>();            
        errors = new List<Record>();
        foreach (Record record in diag)
        {
            var code = Convert.ToInt16(Regex.Split(record.Line, @"\s{1,}")[4], 16);                
            var cond = codes.Where(x => x.Value == code && x.Active).FirstOrDefault();
            if (cond == null)
            {
                errors.Add(record);
                continue;
            }

            var min = record.Datetime.AddSeconds(downDiff);
            var max = record.Datetime.AddSeconds(upDiff);

            //PROBLEM PART - It takes around 4,5ms
            var possibleResults = cas.Where(x => x.Datetime >= min && x.Datetime <= max).ToList();

            if (possibleResults.Count == 0)
                errors.Add(record);
            else
            {                    
                if (!CompareCond(record, possibleResults, cond, ref tempResults, false))
                {                        
                        errors.Add(record);
                }
            }
        }

变量诊断是记录列表

变量 cas 是包含大约 50k 项的记录列表。

问题是它太慢了。第一个 where 子句的部分需要大约 4,6599 毫秒,例如对于 List diag 中的 3000 条记录,它需要 3000*4,6599 = 14 秒。有没有优化代码的选项?

【问题讨论】:

  • 您可以在 forEach 循环之外预过滤 codes 以仅包含活动的项目。这将减少您必须在循环中搜索的数量。
  • 是的,确实如此,谢谢您的通知。我已经做到了。现在,它好一点了,但主要问题仍然是 where 子句与时间。

标签: c# performance linq optimization


【解决方案1】:

你可以加快你强调的特定陈述

cas.Where(x => x.Datetime >= min && x.Datetime <= max).ToList();

cas 列表进行二分搜索。首先按Datetimecas 进行预排序:

cas.Sort((a,b) => a.Datetime.CompareTo(b.Datetime));

然后为Record 创建比较器,它将仅比较Datetime 属性(实现假设列表中没有空记录):

private class RecordDateComparer : IComparer<Record> {
    public int Compare(Record x, Record y) {
        return x.Datetime.CompareTo(y.Datetime);
    }
}

然后你可以像这样翻译你的Where子句:

var index = cas.BinarySearch(new Record { Datetime = min }, new RecordDateComparer());
if (index < 0)
    index = ~index;
var possibleResults = new List<Record>();    
// go backwards, for duplicates            
for (int i = index - 1; i >= 0; i--) {
    var res = cas[i];
    if (res.Datetime <= max && res.Datetime >= min)
        possibleResults.Add(res);
    else break;
}
// go forward until item bigger than max is found
for (int i = index; i < cas.Count; i++) {
    var res = cas[i];
    if (res.Datetime <= max &&res.Datetime >= min)
        possibleResults.Add(res);
    else break;
}    

想法是找到Datetime 等于或大于min 的第一条记录,BinarySearch。如果找到完全匹配 - 它返回匹配元素的索引。如果未找到 - 它返回负值,可以通过~index 操作将其转换为大于目标的第一个元素的索引。

当我们找到那个元素时,我们可以向前移动列表并抓取项目,直到找到Datetime 大于最大值的项目(因为列表已排序)。我们也需要往回走一点,因为如果有重复 - 二进制搜索将不需要返回第一个,所以我们需要向后追溯潜在的重复。

其他改进可能包括:

  • 将活动代码放入 for 循环之外的 Dictionary(由 Value 键入),从而将代码 Where 搜索替换为 Dictionary.ContainsKey

  • 正如 @Digitalsa1nt 在 cmets 中所建议的 - 使用 Parallel.For、PLINQ 或任何类似技术并行化 foreach 循环。这是并行化的完美案例,因为循环仅包含 CPU 绑定的工作。当然,您需要进行一些调整以使其成为线程安全的,例如为errors 使用线程安全集合(或锁定添加到它)。

【讨论】:

  • 谢谢!这很棒。 4.5 毫秒减少到 0.5 毫秒左右,而且好多了。这是我第一次听说 BinarySearch,很高兴知道它。 :)
  • @FilipProcházka 这样就足够快了,或者您想要更快? :)
  • 够了。 :) 但是,如果有另一种优化它的选项,并且您有时间描述/解释如何做到这一点,我喜欢学习它们。 :) 或者只是告诉我我应该搜索什么关键字,我会尝试自己学习/做。
  • @FilipProcházka 我有一个感觉它可以更快,但不能仅用代码提供更多指导,没有该代码的目标。但是您可以将活动代码移出循环并放入HashSet。如果您有很多代码 - 它可能会加快速度。但除此之外,我没有更多的想法。
  • @Digitalsa1nt 是的,实际上这是并行化的完美案例。几乎没有可变的共享状态,CPU 密集型工作。
【解决方案2】:

尝试在列表中添加AsNoTracking

AsNoTracking 方法可以节省执行时间和内存使用量。当我们从数据库中检索大量数据时,应用此选项非常重要。

var possibleResults = cas.Where(x => x.Datetime >= min && x.Datetime <= max).AsNoTracking().ToList(); //around 4,6599ms

【讨论】:

  • 很遗憾,没有效果。
【解决方案3】:

您可以在此处进行一些改进。 这可能只是轻微的性能提升,但在这种情况下,您应该尝试使用 groupby 而不是 where。

所以你应该有这样的东西:

cas.GroupBy(x => x.DateTime >= min && x.DateTime <= max).Select(h => h.Key == true);

这通常适用于在列表中搜索不同的值,但在你的情况下,我不确定它在使用子句时是否会给你带来任何好处。

您还可以在整个代码中执行一些其他操作:

  • 尽可能避免使用 ToList 并坚持使用 IEnumerable。 ToList 会执行急切的评估,这可能会导致您的查询大大减慢。
  • 在检查值是否存在时使用 .Any() 而不是 Count(这仅适用于列表为 IEnumerable 的情况)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-05-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-04-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多