Linq性能:我应该先使用' where '还是' select ' ?

本文关键字:where 还是 select 性能 我应该 Linq | 更新日期: 2023-09-27 18:04:32

我在内存中有一个大的List,来自一个大约有20个properties的类。

我想根据一个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有关,我将数据保存在内存中,或字段的类型。请注意,我也需要这些对象用于其他任务,所以我不能在将它们加载到内存之前首先过滤它们。

Linq性能:我应该先使用' where '还是' select ' ?

先使用Select,还是先使用Where,哪个能给我更好的性能。

Where优先方法性能更高,因为它首先过滤集合,然后仅对过滤的值执行Select

从数学上讲,Where优先的方法采用N + N'操作,其中N'是符合Where条件的集合项的数量。
因此,它最少需要N + 0 = N操作(如果没有项通过Where条件),最多需要N + N = 2 * N操作(如果所有项都通过条件)。

同时,Select first方法将始终完全执行2 * N操作,因为它遍历所有对象以获取属性,然后遍历所有对象以过滤它们。

<<h2>基准证明/h2>

我已经完成了基准来证明我的答案。

结果:

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();
    }
}

相关问题

这些问题也可能让你感兴趣。它们与SelectWhere无关,但它们与LINQ顺序性能有关:

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

答案取决于集合的状态。

  • 如果大多数实体将通过Where测试,则应用先选择;
  • 如果通过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);
    }
}

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();
    }
}

结果:

我多次运行测试,发现

.Select()其中()执行比其中().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

这里是第二个测试结果,我强制每个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

显然,结果非常依赖于集合的状态,因此:

  • 在@YeldarKurmangaliyev的测试.Where().Select()表现更好;,
  • 在我的测试中. select (). where ()表现更好。

集合的状态,我反复提到的包括:

  • 每项的大小;
  • 集合中项目的总数;,
  • 可能通过Where条款的物品数量。

对答案评论的回应:

进一步,@Enigmativity说,提前知道Where的结果,以便知道是先放Where还是先放Select,这是一个第22条军规。从理论上和理论上讲,他是正确的,毫不奇怪,这种情况出现在计算机科学的另一个领域——调度。

最好的调度算法是最短作业优先,即我们首先调度执行时间最短的作业。但是,人们怎么知道完成一项特定的工作需要多少时间呢?那么,答案是:

最短作业next用于可以准确估计运行时间的特定环境。

因此,正如我在上面所说的(这也是我的答案的第一个简短版本),这个问题的正确答案将取决于集合的当前状态。

,

  • 如果您的对象在合理的大小范围内;,
  • 你是选择一个非常小的块从每个对象;,
  • 你的收藏大小也不只是以千计,

那么在这个答案的顶部提到的指导方针将对你有用。