3

我正在努力针对大量POCOs 改进 linq 过滤器的性能,但本地测试表明存在 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 个内核在 1 分 30 秒内达到 100% CPU,这对于生产系统来说是不可行的。我在 VS2012 中运行了性能分析器,30% 的时间是get调用item.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;
}

这仍然达到 100% 的 CPU 和 30% 的item.OrderId通话。过去有没有人遇到过类似的问题,或者可能对如何改进有一些想法?

编辑:代码显示出巨大的改进

感谢@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);
    }
}
4

1 回答 1

3

使用列表字典。

加载项目后,循环它们一次以构建 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);
    }
}
于 2013-05-07T19:55:58.590 回答