C#で巨大なリストをフィルタリングするための最もパフォーマンスの高い方法は?

アレックス

EntityFrameworkを介してSQLServerDBに接続されているASP.NETMVCWebアプリケーションがあります。このアプリケーションの主なタスクの1つは、ユーザーがアーカイブ値を保持する巨大なデータベーステーブルをすばやく検索してフィルタリングできるようにすることです。

テーブル構造は非常に単純です:Timestamp(DateTime)、StationId(int)、DatapointId(int)、Value(double)。このテーブルには、1,000万から1億の行が含まれています。カバーインデックスなどを使用してDBテーブルを最適化しましたが、DatapointId、StationId、Time、およびページに表示したい部分のみをスキップして取得することでフィルタリングすると、ユーザーエクスペリエンスは依然としてかなり遅くなりました。

そこで、別のアプローチを試しました。サーバーには大量のRAMList<ArchiveRow>があるため、Webアプリの起動時にアーカイブテーブル全体をにロードし、ラウンドを実行する代わりに、このリストから直接データを取得できると考えました。-データベースに移動します。これは非常にうまく機能し、アーカイブテーブル全体(現在約1,000万エントリ)をリストにロードするのに約9秒かかります。これArchiveRowは、次のような単純なオブジェクトです。

public class ArchiveResponse {
    public  int Length { get; set; }
    public int numShown { get; set; }
    public  int numFound { get; set; }
    public  int numTotal { get; set; }
    public  List<ArchiveRow> Rows { get; set; }
}

それに応じて:

public class ArchiveRow {
    public  int s { get; set; }
    public  int d { get; set; }
    public  DateTime t { get; set; }
    public  double v { get; set; }
}    

Linqクエリを使用してリストから目的のデータを取得しようとすると、DBのクエリはすでに高速になりますが、複数の条件でフィルタリングすると、それでもかなり低速になります。たとえば、1つのStationIdと12のDatapointIdでフィルタリングすると、25行のウィンドウを取得するのに約5秒かかります。すでにフィルタリングからWhere結合の使用に切り替えましたが、まだ改善の余地があると思います。メモリ消費を可能な限り低く抑えながら、そのようなキャッシュメカニズムを実装するためのより良い方法はありますか?この目的により適した他のコレクションタイプはありますか?

したがって、ArchiveCacheリストから関連データをフィルタリングしてフェッチするコードは次のとおりです。

// Total number of entries in archive cache
var numTotal = ArchiveCache.Count();

// Initial Linq query
ParallelQuery<ArchiveCacheValue> query = ArchiveCache.AsParallel();

// The request may contain StationIds that the user is interested in,
// so here's the filtering by StationIds with a join:
if (request.StationIds.Count > 0)
{
    query = from a in ArchiveCache.AsParallel()
             join b in request.StationIds.AsParallel()
             on a.StationId equals b
             select a;
}

// The request may contain DatapointIds that the user is interested in,
// so here's the filtering by DatapointIds with a join:
if (request.DatapointIds.Count > 0)
{
    query = from a in query.AsParallel()
             join b in request.DatapointIds.AsParallel()
             on a.DataPointId equals b
             select a;
}

// Number of matching entries after filtering and before windowing
int numFound = query.Count();

// Pagination: Select only the current window that needs to be shown on the page
var result = query.Skip(request.Start == 0 ? 0 : request.Start - 1).Take(request.Length);

// Number of entries on the current page that will be shown
int numShown = result.Count();

// Build a response object, serialize it to Json and return to client
// Note: The projection with the Rows is not a bottleneck, it is only done to
// shorten 'StationId' to 's' etc. At this point there are only 25 to 50 rows,
// so that is no problem and happens in way less than 1 ms
ArchiveResponse myResponse = new ArchiveResponse();
myResponse.Length = request.Length;
myResponse.numShown = numShown;
myResponse.numFound = numFound;
myResponse.numTotal = numTotal;
myResponse.Rows = result.Select(x => new archRow() { s = x.StationId, d = x.DataPointId, t = x.DateValue, v = x.Value }).ToList();

return JsonSerializer.ToJsonString(myResponse);

詳細:ステーションの数は通常5から50の間で、50を超えることはめったにありません。データポイントの数は7000未満です。Webアプリケーションは<gcAllowVeryLargeObjects enabled="true" />、web.config設定された64ビットに設定されます。

さらなる改善と推奨事項を本当に楽しみにしています。おそらく、配列などに基づく完全に異なるアプローチがあり、linqなしではるかに優れたパフォーマンスを発揮ますか?

Evk

この特定のクエリタイプに合わせてストレージを調整できます。まず、メモリ内アーカイブから辞書を作成します。

ArchiveCacheByDatapoint = ArchiveCache.GroupBy(c => c.DataPointId)
            .ToDictionary(c => c.Key, c => c.ToList());
ArchiveCacheByStation = ArchiveCache.GroupBy(c => c.StationId)
            .ToDictionary(c => c.Key, c => c.ToList());

次に、クエリでこれらの辞書を使用します。

bool hasStations = request.StationIds.Length > 0;
bool hasDatapoints = request.DatapointIds.Length > 0;            
int numFound = 0;
List<ArchiveCacheValue> result;
if (hasDatapoints && hasStations) {
    // special case - filter by both
    result = new List<ArchiveCacheValue>();
    // store station filter in hash set
    var stationsFilter = new HashSet<int>(request.StationIds);
    // first filter by datapoints, because you have more different datapoints than stations
    foreach (var datapointId in request.DatapointIds.OrderBy(c => c)) {                    
        foreach (var item in ArchiveCacheByDatapoint[datapointId]) {                        
            if (stationsFilter.Contains(item.StationId)) {
                // both datapoint and station matches filter - found item
                numFound++;
                if (numFound >= request.Start && result.Count < request.Length) {
                    // add to result list if matches paging criteria
                    result.Add(item);
                }
            }
        }
    }
}
else if (hasDatapoints) {                
    var query = Enumerable.Empty<ArchiveCacheValue>();                
    foreach (var datapoint in request.DatapointIds.OrderBy(c => c))
    {
        var list = ArchiveCacheByDatapoint[datapoint];
        numFound += list.Count;
        query = query.Concat(list);
    }
    // execute query just once
    result = query.Skip(request.Start).Take(request.Length).ToList();
}
else if (hasStations) {                
    var query = Enumerable.Empty<ArchiveCacheValue>();
    foreach (var station in request.StationIds.OrderBy(c => c))
    {
        var list = ArchiveCacheByStation[station];
        numFound += list.Count;
        query = query.Concat(list);
    }
    // execute query just once
    result = query.Skip(request.Start).Take(request.Length).ToList();
}
else {
    // no need to do Count()
    numFound = ArchiveCache.Count;
    // no need to Skip\Take here really, ArchiveCache is list\array
    // so you can use indexes which will be faster
    result = ArchiveCache.Skip(request.Start).Take(request.Length).ToList();
}

// Number of entries on the current page that will be shown
int numShown = result.Count;

私はそれを測定し、私のマシンでは、1億アイテムについて、試したすべてのタイプのクエリ(セクションのみ、データポイントのみ、セクションとデータポイントの両方)に対して1ミリ秒(場合によっては最大10ミリ秒)で実行されます。

この記事はインターネットから収集されたものであり、転載の際にはソースを示してください。

侵害の場合は、連絡してください[email protected]

編集
0

コメントを追加

0

関連記事

分類Dev

複数の列の複数の値でフィルタリングする最もパフォーマンスの高い方法は?

分類Dev

オブジェクトの配列をフィルタリングするためのパフォーマンスの高い方法

分類Dev

連続ストリーミングデータをフィルタリング(スムーズ)するための最も効率的な方法は何ですか

分類Dev

WPFで管理されていないビデオフレームをレンダリングするための最もパフォーマンスの高い方法は何ですか?

分類Dev

C ++ 11では、参照/ポインタをstd :: string内の位置に戻すための最もパフォーマンスの高い方法は何ですか?

分類Dev

Cでバイナリファイルを解析するためのパフォーマンスが高くクリーンな方法は何ですか?

分類Dev

JSOUPの埋め込みリソースへのリンクを抽出するための最もパフォーマンスの高い方法

分類Dev

iOS:プログラムでスクリーンショットを作成するための最も速くてパフォーマンスの高い方法は何ですか?

分類Dev

Firestoreでドキュメントの大規模なグループの更新をリッスンするパフォーマンスの高い方法は?

分類Dev

「最適なパフォーマンスのためにリポジトリを自動パッキングする」とはどういう意味ですか?

分類Dev

Prisma:サブリレーションをクエリするための最もパフォーマンスの高い方法

分類Dev

ライブストリーミング市場データを処理するためのPythonで最もパフォーマンスの高いデータ構造

分類Dev

LibGDXでタイルをレンダリングするためのパフォーマンスコスト

分類Dev

JSでは、XY座標の整数をすばやく保存するための最もパフォーマンスの高い方法は何ですか

分類Dev

簡単なIPアドレス比較を行うための最もパフォーマンスの高い方法は何ですか?

分類Dev

プロセスパフォーマンスカウンターを照会するための最もパフォーマンスの高い方法は何ですか?

分類Dev

大きな配列にシーケンシャルアイテムのサブセットを設定するための最もパフォーマンスの高い方法は何ですか?

分類Dev

最もパフォーマンスの高いレンダラー、CanvasまたはWebGLを選択する方法

分類Dev

インポートを行うための最もパフォーマンスの高い方法

分類Dev

別のリストの値に基づいてリストから値をフィルタリングする最も効率的な方法は何ですか

分類Dev

Angularでコンポーネントとモジュールをインポートするための最もパフォーマンスの高い方法は何ですか?

分類Dev

Pythonで最もパフォーマンスの高いp値との相関を計算する方法は?

分類Dev

スクロール イベントでユーザーのスクロール位置を取得する最もパフォーマンスの高い方法

分類Dev

リストにない2つの文字を文字列に連結するためのパフォーマンスの高い慣用的な方法

分類Dev

XMLから単一の要素を取得するためのパフォーマンスの高い方法-C#

分類Dev

のもパフォーマンス「フィルタは、マップ」と「その後、フィルタをマッピングする」ストリームが異なっていますか?

分類Dev

サブアレイをフィルタリングするための最もクリーンで最速の方法

分類Dev

リスト内の数字の増加する桁をソート/フィルタリング/合計する最も速い方法は何ですか

分類Dev

C#/。NETメソッドを動的に呼び出す最もパフォーマンスの高い方法

Related 関連記事

  1. 1

    複数の列の複数の値でフィルタリングする最もパフォーマンスの高い方法は?

  2. 2

    オブジェクトの配列をフィルタリングするためのパフォーマンスの高い方法

  3. 3

    連続ストリーミングデータをフィルタリング(スムーズ)するための最も効率的な方法は何ですか

  4. 4

    WPFで管理されていないビデオフレームをレンダリングするための最もパフォーマンスの高い方法は何ですか?

  5. 5

    C ++ 11では、参照/ポインタをstd :: string内の位置に戻すための最もパフォーマンスの高い方法は何ですか?

  6. 6

    Cでバイナリファイルを解析するためのパフォーマンスが高くクリーンな方法は何ですか?

  7. 7

    JSOUPの埋め込みリソースへのリンクを抽出するための最もパフォーマンスの高い方法

  8. 8

    iOS:プログラムでスクリーンショットを作成するための最も速くてパフォーマンスの高い方法は何ですか?

  9. 9

    Firestoreでドキュメントの大規模なグループの更新をリッスンするパフォーマンスの高い方法は?

  10. 10

    「最適なパフォーマンスのためにリポジトリを自動パッキングする」とはどういう意味ですか?

  11. 11

    Prisma:サブリレーションをクエリするための最もパフォーマンスの高い方法

  12. 12

    ライブストリーミング市場データを処理するためのPythonで最もパフォーマンスの高いデータ構造

  13. 13

    LibGDXでタイルをレンダリングするためのパフォーマンスコスト

  14. 14

    JSでは、XY座標の整数をすばやく保存するための最もパフォーマンスの高い方法は何ですか

  15. 15

    簡単なIPアドレス比較を行うための最もパフォーマンスの高い方法は何ですか?

  16. 16

    プロセスパフォーマンスカウンターを照会するための最もパフォーマンスの高い方法は何ですか?

  17. 17

    大きな配列にシーケンシャルアイテムのサブセットを設定するための最もパフォーマンスの高い方法は何ですか?

  18. 18

    最もパフォーマンスの高いレンダラー、CanvasまたはWebGLを選択する方法

  19. 19

    インポートを行うための最もパフォーマンスの高い方法

  20. 20

    別のリストの値に基づいてリストから値をフィルタリングする最も効率的な方法は何ですか

  21. 21

    Angularでコンポーネントとモジュールをインポートするための最もパフォーマンスの高い方法は何ですか?

  22. 22

    Pythonで最もパフォーマンスの高いp値との相関を計算する方法は?

  23. 23

    スクロール イベントでユーザーのスクロール位置を取得する最もパフォーマンスの高い方法

  24. 24

    リストにない2つの文字を文字列に連結するためのパフォーマンスの高い慣用的な方法

  25. 25

    XMLから単一の要素を取得するためのパフォーマンスの高い方法-C#

  26. 26

    のもパフォーマンス「フィルタは、マップ」と「その後、フィルタをマッピングする」ストリームが異なっていますか?

  27. 27

    サブアレイをフィルタリングするための最もクリーンで最速の方法

  28. 28

    リスト内の数字の増加する桁をソート/フィルタリング/合計する最も速い方法は何ですか

  29. 29

    C#/。NETメソッドを動的に呼び出す最もパフォーマンスの高い方法

ホットタグ

アーカイブ