首页 > 解决方案 > C# - Linq 使用 List 和 Where 子句优化代码

问题描述

我有以下代码:

        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);
                }
            }
        }

变量 diag 是记录列表

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

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

标签: c#performancelinqoptimization

解决方案


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

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

cas列表进行二分搜索。首先预排序casDatetime

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相等或更大的第一条记录。如果找到完全匹配 - 它返回匹配元素的索引。如果未找到 - 它返回负值,可以通过操作将其转换为大于目标的第一个元素的索引。minBinarySearch~index

当我们找到那个元素时,我们可以向前移动列表并抓取项目,直到找到Datetime大于最大值的项目(因为列表已排序)。我们还需要向后退一点,因为如果有重复 - 二分查找将不必返回第一个,所以我们需要向后退以寻找潜在的重复。

其他改进可能包括:

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

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


推荐阅读