LINQ関数の順序は重要ですか?


114

基本的に、質問のように... LINQ関数の順序はパフォーマンスの点で重要ですか?もちろん、結果は同じでなければなりません...

例:

myCollection.OrderBy(item => item.CreatedDate).Where(item => item.Code > 3);
myCollection.Where(item => item.Code > 3).OrderBy(item => item.CreatedDate);

どちらも同じ結果を返しますが、LINQの順序が異なります。いくつかのアイテムを並べ替えると結果が異なることに気づき、それらについては気にしません。同じ結果を得るために、順序付けがパフォーマンスに影響を与える可能性があるかどうかを知ることが私の主な関心事です。そして、私が行った2つのLINQ呼び出し(OrderBy、Where)だけでなく、すべてのLINQ呼び出しについてもです。


9
素晴らしい質問です。
Robert S.

プロバイダーの最適化がのようなより奇抜なケースで重要であることはさらに明白ですvar query = myCollection.OrderBy(item => item.Code).Where(item => item.Code == 3);
Mark Hurd、2011

1
あなたは賛成票に値します:)、興味深い質問。LinqをEFのエンティティに書き込むときに検討します。
GibboK 2011

1
@GibboK:LINQクエリを「最適化」しようとするときは注意してください(下の回答を参照)。時には、実際には何も最適化しないこともあります。最適化を試みる場合は、プロファイラーツールを使用することをお勧めします。
myermian

回答:


147

使用しているLINQプロバイダーによって異なります。LINQ to Objectsの場合、それは確かに大きな違いをもたらす可能性があります。実際に持っていると仮定します:

var query = myCollection.OrderBy(item => item.CreatedDate)
                        .Where(item => item.Code > 3);

var result = query.Last();

そのためにはコレクション全体をソートしてからフィルタリングする必要があります。100万個のアイテムがあり、そのうちの1つだけが3より大きいコードを持っている場合、破棄される結果の順序付けに多くの時間を費やすことになります。

それを逆の操作と比較して、最初にフィルタリングします。

var query = myCollection.Where(item => item.Code > 3)
                        .OrderBy(item => item.CreatedDate);

var result = query.Last();

今回はフィルター処理された結果のみを並べ替えます。「フィルターに一致する単一のアイテム」のサンプルの場合は、時間と空間の両方ではるかに効率的です。

また、可能性があり、クエリが正しくないか、実行するかどうかの違いを作ります。考慮してください:

var query = myCollection.Where(item => item.Code != 0)
                        .OrderBy(item => 10 / item.Code);

var result = query.Last();

これで問題ありません。0で除算されることは決してないでしょう。しかし、フィルタリングのに順序付けを実行すると、クエリは例外をスローします。


2
@Jon Skeet、各LINQプロバイダーと関数のBig-Oに関するドキュメントはありますか?それとも単に「それぞれの表現は状況に固有のもの」のケースなのか。
マイケル

1
@michael:それはあまり明確に文書化されていませんが、私の「Edulinq」ブログシリーズを読んだ場合、私はそれについてかなり詳細に話していると思います。
Jon Skeet、2011


3
@gdoron:正直言って、あなたが何を言っているのか本当にはっきりしていません。新しい質問を書きたいと思うかもしれません。Queryableはクエリをまったく解釈しようとしていないことに注意してください。その役割は、他の何かが解釈できるようにクエリを保存することだけです。また、LINQ to Objectsは式ツリーを使用していません。
Jon Skeet、2011

1
@gdoron:ポイントは、それはプロバイダーの仕事であり、Queryableの仕事ではありません。また、Entity Frameworkを使用する場合も問題になりません。それはないもののオブジェクトへのLINQのための問題。しかし、はい、必ず別の質問をしてください。
Jon Skeet

17

はい。

ただしそのパフォーマンスの違いは、基になる式ツリーがLINQプロバイダーによってどのように評価されるかによって異なります。

たとえば、LINQ-to-XMLの場合、クエリは2回目に(最初にWHERE句を使用して)高速に実行できますが、LINQ-to-SQLの場合は初回は高速です。

パフォーマンスの違いを正確に知るには、アプリケーションのプロファイルを作成する必要があります。ただし、このような場合でも、時期尚早の最適化は通常、努力する価値はありません。LINQのパフォーマンス以外の問題の方が重要であることに気付くかもしれません。


5

特定の例で、パフォーマンスに影響を与える可能性があります。

最初のクエリ:OrderBy呼び出しは、が3以下のアイテムを含むソースシーケンス全体を反復処理する必要がCodeあります。次に、Where句は順序付けされたシーケンス全体を反復する必要もあります。

2番目のクエリ:Where呼び出しは、シーケンスCodeが3より大きいアイテムのみに制限OrderByWhereます。呼び出しは、呼び出しによって返された縮小されたシーケンスのみをトラバースする必要があります。


3

Linq-To-Objectsの場合:

ソートはかなり遅く、O(n)メモリを使用します。Where一方、比較的高速で、一定のメモリを使用します。そのWhereため、最初に行う方が速く、大規模なコレクションの場合は著しく速くなります。

大きなオブジェクトヒープへの割り当て(それらのコレクションと共に)は、私の経験では比較的高価であるため、メモリ負荷の軽減も重要になる可能性があります。


1

明らかに、結果はまだ同じでなければなりません...

これは実際には当てはまらないことに注意してください。特に、次の2行は異なる結果になります(ほとんどのプロバイダー/データセットの場合)。

myCollection.OrderBy(o => o).Distinct();
myCollection.Distinct().OrderBy(o => o);

1
いいえ、私が意味したのは、最適化を考慮しても結果は同じであるべきだということです。何かを「最適化」して別の結果を得る意味はありません。
マイケル

1

LINQクエリを最適化する方法を検討する場合は注意が必要です。たとえば、LINQの宣言バージョンを使用して次のことを行う場合:

public class Record
{
    public string Name { get; set; }
    public double Score1 { get; set; }
    public double Score2 { get; set; }
}


var query = from record in Records
            order by ((record.Score1 + record.Score2) / 2) descending
            select new
                   {
                       Name = record.Name,
                       Average = ((record.Score1 + record.Score2) / 2)
                   };

何らかの理由で、最初に平均を変数に格納してクエリを「最適化」することに決めた場合、希望する結果が得られません。

// The following two queries actually takes up more space and are slower
var query = from record in Records
            let average = ((record.Score1 + record.Score2) / 2)
            order by average descending
            select new
                   {
                       Name = record.Name,
                       Average = average
                   };

var query = from record in Records
            let average = ((record.Score1 + record.Score2) / 2)
            select new
                   {
                       Name = record.Name,
                       Average = average
                   }
            order by average descending;

オブジェクトに宣言型LINQを使用する人は少ないと思いますが、考えるのに適した食べ物です。


0

それは関連性に依存します。Code = 3のアイテムが非常に少ない場合、次の注文は少量のコレクションで機能し、日付順に注文を取得します。

一方、同じCreatedDateのアイテムが多数ある場合、次の注文はより大きなコレクションセットで機能し、日付順に注文を取得します。

したがって、どちらの場合もパフォーマンスに違いがあります

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.