Linq表现:我应该首先使用`where`或`select`

Akb*_*ari 10 c# linq performance linq-to-entities query-performance

我的List记忆力很大,来自一个大约20岁的班级properties.

我想基于一个过滤此列表property,对于特定任务,我只需要一个列表property.所以我的查询是这样的:

data.Select(x => x.field).Where(x => x == "desired value").ToList()
Run Code Online (Sandbox Code Playgroud)

哪一个让我有更好的表现,Select先使用或使用Where

data.Where(x => x.field == "desired value").Select(x => x.field).ToList()
Run Code Online (Sandbox Code Playgroud)

如果这与data type我将数据保存在内存或字段类型中有关,请告诉我.请注意,我也需要这些对象用于其他任务,因此我无法在首先过滤它们并将它们加载到内存之前.

Yel*_*yev 8

哪一个让我有更好的表现,先使用Select,或使用Where.

Where第一种方法性能更高,因为它首先过滤您的集合,然后仅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
Run Code Online (Sandbox Code Playgroud)

如果您多次运行基准测试,那么您将看到该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();
    }
}
Run Code Online (Sandbox Code Playgroud)

相关问题

这些问题对您来说也很有趣.它们与Selectand 不相关Where,但它们与LINQ订单性能有关:

LINQ函数的顺序是否重要?
LINQ扩展方法的顺序不影响性能?


dis*_*ame 5

答案将取决于您的收藏状态。

  • 如果大多数实体将通过“ 地方”测试,则首先应用“ 选择”
  • 如果更少的实体将通过Where测试,请首先应用Where

更新:

@YeldarKurmangaliyev给出了具体示例和基准测试的答案。我运行了类似的代码来验证他的主张,而我们的结果却完全相反,这是因为我运行了与他相同的测试,但是对象并不像Point他用于运行测试的类型那样简单。

该代码非常类似于他的代码,除了我将类的名称从更改PointEnumerableClass

在下面给出的类中,我曾经构成了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);
    }
}
Run Code Online (Sandbox Code Playgroud)

该课程FrameGatoradeHome各有一个静态方法来返回其类型的实例。

下面是主程序:

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

结果:

我多次运行测试,发现

.Select()。Where()的 性能优于 .Where()。Select()。

当集合大小为1000000时。


这是我将每个EnumerableClass对象的Y值强制为5 的第一个测试结果,因此每个项目都通过了其中

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

First Where's first item: 5
First Select's first item: 5
Run Code Online (Sandbox Code Playgroud)

这是第二个测试结果,其中我强制每个EnumerableClass对象的Y值均为251,因此没有任何项目通过其中

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

First Where's first item: 0
First Select's first item: 0
Run Code Online (Sandbox Code Playgroud)

显然,结果非常依赖于集合的状态,以至于

  • 在@YeldarKurmangaliyev的测试中,.Where()。Select()性能更好;和,
  • 在我的测试中,.Select()。Where()性能更好。

状态的集合,我提了个遍包括:

  • 每个项目的大小;
  • 集合中的项目总数;和,
  • 可能通过Where子句的项目数。

对答案的回应:

此外,@ Enigmativity表示,提前知道Where的结果是为了知道是将Where放在首位还是将Select放在首位是Catch-22。从理论上讲,他是正确的,并且毫不奇怪,这种情况在计算机科学的另一个领域- 调度中可以看到。

最好的调度算法是“ 最短作业优先”,其中我们首先调度将执行最少时间的作业。但是,谁会知道某项工作需要花费多少时间呢?好吧,答案是:

接下来最短的作业用于可以准确估计运行时间的专业环境。

因此,正如我在顶部说的那样(也是我回答的第一个简短版本),对这个问题的正确答案将取决于集合当前状态

一般来说,

  • 如果您的物体在合理的尺寸范围内;和,
  • 您正在从每个对象中选择一个很小的块;和,
  • 您的收藏规模也不只是数千个,

那么该答案顶部右侧提到的指南将对您有用。