Чи має значення порядок функцій 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
Дивовижне питання.
Роберт С.

Це ще очевидніше, що оптимізація постачальника стосується більш педантичного випадку var query = myCollection.OrderBy(item => item.Code).Where(item => item.Code == 3);.
Марк Херд

1
Ви заслуговуєте Up Vote :), цікаві запитання. Я буду розглядати це, коли напишу свою Linq суб'єктам господарювання в EF.
GibboK

1
@GibboK: Будьте уважні, намагаючись "оптимізувати" ваші LINQ запити (див. Відповідь нижче). Іноді ви не закінчуєте насправді нічого оптимізувати. Найкраще використовувати інструмент профілера при спробі оптимізації.
myermian

Відповіді:


147

Це залежатиме від постачальника LINQ, який використовується. Що стосується LINQ до об'єктів, це, безумовно, може змінити величезну зміну. Припустимо, що насправді у нас є:

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

var result = query.Last();

Це вимагає , щоб вся колекція буде впорядкований і потім фільтрують. Якби у нас був мільйон предметів, лише в одному з яких був код більше 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, Чи є документація про Big-O для кожного з постачальників та функцій LINQ? Або це лише випадок «кожен вираз унікальний для ситуації».
michael

1
@michael: Це не дуже чітко задокументовано, але якщо ви читаєте мою серію блогу "Edulinq", я думаю, я про це розповідаю досить розумно.
Джон Скіт

3
@michael: ви можете знайти тут msmvps.com/blogs/jon_skeet/archive/tags/Edulinq/default.aspx
VoodooChild

3
@gdoron: Не зовсім зрозуміло, що ти маєш на увазі, якщо чесно. Здається, ви можете написати нове запитання. Майте на увазі, що Queryable взагалі не намагається інтерпретувати ваш запит - його завдання полягає лише в тому, щоб зберегти ваш запит, щоб щось інше могло інтерпретувати його. Також зауважте, що LINQ для об'єктів навіть не використовує дерева виразів.
Джон Скіт

1
@gdoron: Справа в тому, що робота провайдера, а не робота Queryable. І це також не має значення при використанні Entity Framework. Це робить справу LINQ до об'єктів , хоча. Але так, обов'язково задайте інше питання.
Джон Скіт

17

Так.

Але саме те , що різниця в продуктивності, залежить від того, як лежить в основі виразу дерева оцінюється постачальником LINQ.

Наприклад, ваш запит може виконуватись швидше вдруге (з першого пункту WHERE) для LINQ-to-XML, але швидше в перший раз для LINQ-до-SQL.

Щоб точно з’ясувати, в чому різниця в продуктивності, ви, швидше за все, захочете профайлювати свою заявку. Як і колись у подібних речах, передчасна оптимізація, як правило, не вартує зусиль - ви, можливо, виявите, що проблеми, ніж продуктивність LINQ, важливіші.


5

У вашому конкретному прикладі це може змінити продуктивність.

Перший запит: Ваш OrderByдзвінок потрібно повторити через всю послідовність джерел, включаючи ті елементи, де Code3 або менше. Тоді цей Whereпункт також повинен повторити всю упорядковану послідовність.

Другий запит: WhereВиклик обмежує послідовність лише на тих пунктах, де Codeбільше 3. OrderByВиклик тоді повинен лише пройти зменшену послідовність, повернуту Whereвикликом.


3

У Linq-To-Objects:

Сортування досить повільне і використовує O(n)пам'ять. Whereз іншого боку, відносно швидкий і використовує постійну пам'ять. Так що робити Whereспочатку буде швидше, а для великих колекцій значно швидше.

Знижений тиск пам’яті теж може бути суттєвим, оскільки виділення на великій купі об’єктів (разом з їх колекцією) є досить дорогими на мій досвід.


1

Очевидно, результати повинні були б бути однаковими ...

Зауважте, що це насправді не так - зокрема, наступні два рядки дадуть різні результати (для більшості постачальників / наборів даних):

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

1
Ні, я мав на увазі, що результати повинні бути ідентичними, щоб навіть врахувати оптимізацію. Немає сенсу щось «оптимізувати» і отримати інший результат.
michael

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

Це залежить від релевантності. Припустимо, якщо у вас дуже мало предметів з кодом = 3, наступне замовлення буде працювати над невеликим набором колекцій, щоб отримати замовлення за датою.

Якщо у вас є багато предметів з тим же CreateDate, наступне замовлення буде працювати над більшим набором колекції, щоб отримати замовлення за датою.

Отже, в обох випадках буде різниця у продуктивності

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.