web-dev-qa-db-ja.com

Linqのパフォーマンス:最初に `where`または` select`を使用する必要があります

約20個のListを持つクラスから、メモリに大きなpropertiesがあります。

このリストを1つのpropertyだけに基づいてフィルタリングしたいのですが、特定のタスクでは、そのpropertyのリストだけが必要です。したがって、私のクエリは次のようになります。

data.Select(x => x.field).Where(x => x == "desired value").ToList()

最初にSelectを使用するか、Whereを使用するか、どちらがパフォーマンスを向上させますか?

data.Where(x => x.field == "desired value").Select(x => x.field).ToList()

これがdata typeに関連しているかどうかを教えてください。データをメモリに保持しているのか、フィールドのタイプです。これらのオブジェクトは他のタスクにも必要であるため、最初からメモリにロードする前にフィルタリングすることはできません。

11
Akbari

[最初に選択]を使用するか、[Where]を使用すると、どちらがパフォーマンスが向上します。

Where最初のアプローチは、コレクションを最初にフィルタリングしてから、filtered値に対してのみSelectを実行するため、パフォーマンスが向上します。 。

数学的に言えば、Where-最初のアプローチはN + N'操作、ここでN'は、Where条件に該当するコレクションアイテムの数です。
つまり、N + 0 = N最小の操作(アイテムがこのWhere条件を通過しない場合)およびN + N = 2 * N最大での操作(すべてのアイテムが条件に合格した場合)。

同時に、Select最初のアプローチは常に正確に2 * N操作。これは、すべてのオブジェクトを反復処理してプロパティを取得してから、すべてのオブジェクトを反復処理してそれらをフィルタリングするためです。

ベンチマーク証明

私は自分の答えを証明するためにベンチマークを完了しました。

結果:

Condition value: 50
Where -> Select: 88 ms, 10500319 hits
Select -> Where: 137 ms, 20000000 hits

Condition value: 500
Where -> Select: 187 ms, 14999212 hits
Select -> Where: 238 ms, 20000000 hits

Condition value: 950
Where -> Select: 186 ms, 19500126 hits
Select -> Where: 402 ms, 20000000 hits

ベンチマークを何度も実行すると、Where -> Selectアプローチヒットは時々変化しますが、Select -> Whereアプローチは常に2N オペレーション。

IDEOneのデモ:

https://ideone.com/jwZJLt

コード:

class Point
{
    public int X { get; set; }
    public int Y { get; set; }
}

class Program
{
    static void Main()
    {
        var random = new Random();
        List<Point> points = Enumerable.Range(0, 10000000).Select(x => new Point { X = random.Next(1000), Y = random.Next(1000) }).ToList();

        int conditionValue = 250;
        Console.WriteLine($"Condition value: {conditionValue}");

        Stopwatch sw = new Stopwatch();
        sw.Start();

        int hitCount1 = 0;
        var points1 = points.Where(x =>
        {
            hitCount1++;
            return x.X < conditionValue;
        }).Select(x =>
        {
            hitCount1++;
            return x.Y;
        }).ToArray();

        sw.Stop();
        Console.WriteLine($"Where -> Select: {sw.ElapsedMilliseconds} ms, {hitCount1} hits");

        sw.Restart();

        int hitCount2 = 0;
        var points2 = points.Select(x =>
        {
            hitCount2++;
            return x.Y;
        }).Where(x =>
        {
            hitCount2++;
            return x < conditionValue;
        }).ToArray();

        sw.Stop();
        Console.WriteLine($"Select -> Where: {sw.ElapsedMilliseconds} ms, {hitCount2} hits");

        Console.ReadLine();
    }
}

関連する質問

これらの質問もあなたにとって興味深いものになる可能性があります。これらはSelectおよびWhereとは関係ありませんが、LINQ注文のパフォーマンスに関するものです。

LINQ関数の順序は重要ですか?
LINQ拡張メソッドの順序はパフォーマンスに影響しませんか?

答えは、コレクションの状態によって異なります。

  • ほとんどのエンティティがWhereテストに合格する場合は、Selectを最初に適用します。
  • Whereテストに合格するエンティティが少ない場合は、最初にWhereを適用します。

更新:

@YeldarKurmangaliyevは、具体的な例とベンチマークを使用して回答を作成しました。私は彼の主張を検証するために同様のコードを実行しましたそして私たちの結果は正反対ですそしてそれは私が彼と同じテストを実行したがオブジェクトがそれほど単純ではないためです彼がテストを実行するために使用したPointタイプとして。

クラスの名前をPointからEnumerableClassに変更したことを除けば、コードは彼のコードと非常によく似ています。

以下に、私がEnumerableClassクラスを構成するために使用したクラスを示します。

public class EnumerableClass
{
    public int X { get; set; }
    public int Y { get; set; }
    public String A { get; set; }
    public String B { get; set; }
    public String C { get; set; }
    public String D { get; set; }
    public String E { get; set; }
    public Frame F { get; set; }
    public Gatorade Gatorade { get; set; }
    public Home Home { get; set; }
}

public class Home
{
    private Home(int rooms, double bathrooms, Stove stove, InternetConnection internetConnection)
    {
        Rooms = rooms;
        Bathrooms = (decimal) bathrooms;
        StoveType = stove;
        Internet = internetConnection;
    }

    public int Rooms { get; set; }
    public decimal Bathrooms { get; set; }
    public Stove StoveType { get; set; }
    public InternetConnection Internet { get; set; }

    public static Home GetUnitOfHome()
    {
        return new Home(5, 2.5, Stove.Gas, InternetConnection.Att);
    }
}

public enum InternetConnection
{
    Comcast = 0,
    Verizon = 1,
    Att = 2,
    Google = 3
}

public enum Stove
{
    Gas = 0,
    Electric = 1,
    Induction = 2
}

public class Gatorade
{
    private Gatorade(int volume, Color liquidColor, int bottleSize)
    {
        Volume = volume;
        LiquidColor = liquidColor;
        BottleSize = bottleSize;
    }

    public int Volume { get; set; }
    public Color LiquidColor { get; set; }
    public int BottleSize { get; set; }

    public static Gatorade GetGatoradeBottle()
    {
        return new Gatorade(100, Color.Orange, 150);
    }
}

public class Frame
{
    public int X { get; set; }
    public int Y { get; set; }

    private Frame(int x, int y)
    {
        X = x;
        Y = y;
    }

    public static Frame GetFrame()
    {
        return new Frame(5, 10);
    }
}

クラスFrameGatorade、およびHomeには、それぞれのタイプのインスタンスを返す静的メソッドがあります。

以下はメインプログラムです:

public static class Program
{
    const string Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
    private static readonly Random Random = new Random();

    private static string RandomString(int length)
    {
        return new string(Enumerable.Repeat(Chars, length)
            .Select(s => s[Random.Next(s.Length)]).ToArray());
    }

    private static void Main()
    {
        var random = new Random();
        var largeCollection =
            Enumerable.Range(0, 1000000)
                .Select(
                    x =>
                        new EnumerableClass
                        {
                            A = RandomString(500),
                            B = RandomString(1000),
                            C = RandomString(100),
                            D = RandomString(256),
                            E = RandomString(1024),
                            F = Frame.GetFrame(),
                            Gatorade = Gatorade.GetGatoradeBottle(),
                            Home = Home.GetUnitOfHome(),
                            X = random.Next(1000),
                            Y = random.Next(1000)
                        })
                .ToList();

        const int conditionValue = 250;
        Console.WriteLine(@"Condition value: {0}", conditionValue);

        var sw = new Stopwatch();
        sw.Start();
        var firstWhere = largeCollection
            .Where(x => x.Y < conditionValue)
            .Select(x => x.Y)
            .ToArray();
        sw.Stop();
        Console.WriteLine(@"Where -> Select: {0} ms", sw.ElapsedMilliseconds);

        sw.Restart();
        var firstSelect = largeCollection
            .Select(x => x.Y)
            .Where(y => y < conditionValue)
            .ToArray();
        sw.Stop();
        Console.WriteLine(@"Select -> Where: {0} ms", sw.ElapsedMilliseconds);
        Console.ReadLine();

        Console.WriteLine();
        Console.WriteLine(@"First Where's first item: {0}", firstWhere.FirstOrDefault());
        Console.WriteLine(@"First Select's first item: {0}", firstSelect.FirstOrDefault());
        Console.WriteLine();
        Console.ReadLine();
    }
}

結果:

テストを複数回実行したところ、

。Select()。Where()より良いパフォーマンス。Where()。 Select()。

コレクションサイズが1000000の場合。


これは、すべてのEnumerableClassオブジェクトのY値を5に強制した最初のテスト結果であるため、すべてのアイテムが渡されましたWhere

Condition value: 250
Where -> Select: 149 ms
Select -> Where: 115 ms

First Where's first item: 5
First Select's first item: 5

これは、すべてのEnumerableClassオブジェクトのY値を251に強制したため、アイテムが渡されなかった2番目のテスト結果ですWhere

Condition value: 250
Where -> Select: 110 ms
Select -> Where: 100 ms

First Where's first item: 0
First Select's first item: 0

明らかに、結果はコレクションのstateに大きく依存しているため、

  • @YeldarKurmangaliyevのテストでは。Where()。Select()パフォーマンスが向上しました。そして、
  • 私のテストでは。Select()。Where()パフォーマンスが向上しました。

私が何度も言及しているコレクションのstateには、次のものが含まれます。

  • 各アイテムのサイズ。
  • コレクション内のアイテムの総数。そして、
  • Where句を通過する可能性のあるアイテムの数。

回答に対するコメントへの回答:

さらに、@ Enigmativityは、Whereを最初に置くかSelectを最初にするかを知るために、Whereの結果を事前に知ることはキャッチであると述べました。 -22。理想的かつ理論的には、彼は正しく、当然のことながら、この状況はコンピュータサイエンスの別のドメインで見られます スケジューリング

最適なスケジューリングアルゴリズムは Shortest Job First で、実行時間が最短になるジョブを最初にスケジュールします。しかし、特定の仕事が完了するまでにどれくらいの時間がかかるかを誰かがどうやって知るのでしょうか?まあ、答えはそれです:

次の最短ジョブは、実行時間の正確な見積もりが可能な特殊な環境で使用されます。

したがって、一番上で言ったように(これは私の答えの最初の短いバージョンでもありました)、この質問に対する正しい答えは、コレクションの現在の状態

一般に、

  • オブジェクトが妥当なサイズ範囲内にある場合。そして、
  • あなたはSelect各オブジェクトから非常に小さなチャンクを取り出しています。そして、
  • コレクションのサイズも数千にすぎません。

そうすれば、この回答の冒頭に記載されているガイドラインが役に立ちます。

5
displayName