3

s の大規模なコレクションに対する linq フィルターのパフォーマンスの改善に取り組んでいますPOCOが、ローカル テストでは CPU のボトルネックが示されています。

私は当初、大きな結果セットを取得して別の処理サーバーのメモリにロードし、この結果セットを .Net でフィルタリングすることで、SQL サーバーの負荷を軽減するためにこれを実行しようとしていました。

デモコードは次のとおりです。

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 を使用しており、item.OrderId通話中は 30% に達しています。誰かが過去に同様の問題を抱えていましたか、それともおそらくこれを改善する方法について何らかの考えを持っていますか?

編集:大幅な改善を示すコード

@FastAl のおかげで、このコードは_bar->DoOtherStuff(filteredItems)ループを 1 秒以内に実行しました。

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分しかありませんでした;-) 辞書を愛することを学びましょう. 非常に高速です - 最速の検索/挿入メソッドの 1 つを使用します。M $ の本当に素晴らしい小さなツールです。

私の正直な提案 - データベースでそれを行います。自問してみてください - あなたはそこで試しましたか?私はこれにしばらく取り組んできましたが、最初に実際にテストしない限り、2つの未知数のどちらがより高速になるかはまだわかりません(それが本当に明らかでない限り、もしそうならここに投稿しなかったでしょう)。DB に 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 に答える