有效地过滤大量POCO实体集合

本文关键字:实体 集合 POCO 过滤 有效地 | 更新日期: 2023-09-27 18:12:35

我正在努力提高linq过滤器对大量POCO的性能,但是本地测试表明存在CPU瓶颈。

我最初试图这样做是为了减少SQL服务器上的负载,通过检索一个大的结果集并将其加载到一个单独的处理服务器的内存中,然后在。net中过滤这个结果集。

下面是演示代码:
public class CustomClass
{
    public int Id { get; set; }
    public int OtherId { get; set;}
    public DateTime Date { get; set; }
}
public void DoStuff()
{        
    // approx 800,000 items
    List<CustomClass> allItems = _repo.GetCustomClassItemsFromDatabase();
    foreach (OtherCustomClass foo in _bar)
    {
        // original linq-to-entities query,
        // get most recent Ids that apply to OtherId
        List<CustomClass> filteredItems = (
            from item in allItems
            where item.OtherId == foo.OtherId && item.Date <= foo.Date
            group item by item.Id into groupItems
            select groupItems.OrderByDescending(i => i.Date).First()).ToList();
        DoOtherStuff(filteredItems);
    }
}

这将使我的4核在1m30s内达到100%的CPU,并且对于生产系统是不可用的。我在VS2012中运行性能分析器,30%的时间是getitem.OtherId的调用。

我开始将linq重写为纯代码,看看是否可以提高速度,但到目前为止我还没有任何运气。下面是重写的纯代码:

private List<CustomClass> FilterCustomClassByIdAndDate(
    List<CustomClass> items, int id, DateTime date)
{
    var mostRecentCustomClass = new Dictionary<int, CustomClass>();
    foreach (CustomClass item in items)
    {
        if (item.Id != id || item.Date > date) { continue; }
        CustomClass mostRecent;
        if (mostRecentCustomClass.TryGetValue(item.Id, out mostRecent) &&
            mostRecent.Date >= item.Date) 
        { continue; }
        mostRecentCustomClass[item.Id] = item;
    }
    var filteredItems = new List<CustomClass>();
    foreach (KeyValuePair<int, CustomClass> pair in mostRecentCustomClass)
    {
        filteredItems.Add(pair.Value);
    }
    return filteredItems;
}

item.OrderId调用上仍然达到100% CPU和30%。有没有人在过去有过类似的问题,或者也许有一些关于如何改善这一点的想法?

编辑:显示大量改进的代码

多亏了@FastAl,这段代码在不到一秒的时间内运行了_bar -> DoOtherStuff(filteredItems)循环:

public void DoStuff()
{        
    // approx 800,000 items
    List<CustomClass> allItems = _repo.GetCustomClassItemsFromDatabase();
    var indexedItems = new Dictionary<int, List<CustomClass>>();
    foreach (CustomClass item in allItems)
    {
        List<CustomClass> allByOtherId;
        if (!indexedItems.TryGetValue(item.OtherId, out allByOtherId)) 
        {
            allByOtherId = new List<CustomClass>();
            indexedItems[item.OtherId] = allByOtherId;
        }
        allByOtherId.Add(item);
    }
    foreach (OtherCustomClass foo in _bar)
    {
        List<CustomClass> filteredItems;
        if (!indexedItems.ContainsKey(foo.OtherId))
        {
            filteredItems = new List<CustomClass>();
        }
        else
        {
            List<CustomClass> filteredItems = (
                from item in indexedItems[foo.OtherId]
                where item.Date <= foo.Date
                group item by item.Id into groupItems
                select groupItems.OrderByDescending(i => i.Date).First())
                .ToList();
        }
        DoOtherStuff(filteredItems);
    }
}

有效地过滤大量POCO实体集合

使用列表字典

加载项后,循环遍历它们一次以构建list的字典。注意插入的循环和where子句中的更改。

请原谅我的错误,我只有4分钟;-)学会爱字典。它非常快——使用了最快的搜索/插入方法之一。M$.

我诚实的建议-在数据库上做。问问你自己——你在那里试过吗?我在这方面已经有一段时间了,如果不先进行实际测试,我永远无法判断两个未知数中哪一个更快(除非它真的很明显,但如果是的话,你就不会在这里发布这篇文章了)。仔细检查数据库在OtherID上有索引,否则它面临同样的问题你的linq语句是(线性搜索)。

public class CustomClass
{
    public int Id { get; set; }
    public int OtherId { get; set;}
    public DateTime Date { get; set; }
}
public void DoStuff()
{        
    // approx 800,000 items
    List<CustomClass> allItems = _repo.GetCustomClassItemsFromDatabase();
    var index1 = new Dictionary <int, CustomClass>; 
    foreach (OtherCustomClass foo1 in allItems)
    {
        List<CustomClass> allOtherIDs ;
        allOtherIDs=null;
        if (!index1.TryGetValue(foo1.OtherID,allOtherIDs))
         {
            allOtherIDs=new List<CustomClass>;
            index1.add(foo1.OtherID,allOtherIDs);
        }
        allOtherIDs(foo1.OtherID)=foo1;
    }

    foreach (OtherCustomClass foo in _bar)
    {
        // original linq-to-entities query,
        // get most recent Ids that apply to OtherId
        List<CustomClass> filteredItems = (
            from item in allOtherIDs(foo.OtherID)
            where item.Date <= foo.Date
            group item by item.Id into groupItems
            select groupItems.OrderByDescending(i => i.Date).First()).ToList();
        DoOtherStuff(filteredItems);
    }
}