Есть следующий код:
using System.Diagnostics;
class A
{
public int Count { get; set; }
}
var rnd = new Random();
var lst1 = new List<A>();
var lst2 = new List<A>();
for (int i = 0; i < 10000; i++)
{
lst1.Add(new A { Count = rnd.Next(10000) });
if (i % 3 == 0)
{
lst2.Add(new A { Count = rnd.Next(10000) });
}
}
var sw = new Stopwatch();
for (int i = 0; i < 10; i++)
{
sw.Start();
var res1 = lst2.Where(i => !lst1.Select(l => l.Count).Contains(i.Count));
sw.Stop();
Console.WriteLine(res1.Count() + " " + sw.Elapsed);
sw.Reset();
}
Первая итерация цикла for при вычислении res1 всегда выполняется существенно медленнее, чем следующие, т.е. после первой итерации результат LINQ кэшируется? И почему если вынести lst1.Select(l => l.Count) внутри for в отдельную переменную результат будет только хуже в плане скорости?
Как уже написал в комментариях tym32167, одна из причин - JIT-компиляция кода CIL.
То есть при исполнении вот этой строки:
var res1 = lst2.Where(a => !lst1.Select(l => l.Count).Contains(a.Count));
сперва компилируются все эти методы: Where
, Select
, Contains
. Классы List<A>
, A
и его свойство Count
уже использовались выше по коду, поэтому они уже скомпилированы.
Что важно: недостаточно просто вызвать эти методы, например, так:
var list = new List<string>();
var test = list.Where(a => a == null).Select(a => a).Contains(null);
Результат замеров от этого не изменится. Хотя, казалось бы, использованы именно эти методы, но нет, у них другая сигнатура, а именно: с параметром List<string>
.
А если взять наш класс A
:
var list = new List<A>();
var test = list.Where(a => a == null).Select(a => a).Contains(null);
то это сразу скажется на результате замеров со Stopwatch
.
А теперь самое важное. Вот эта строка:
var res1 = lst2.Where(a => !lst1.Select(l => l.Count).Contains(a.Count));
ничего не делает (JIT-компиляция не в счёт). Здесь просто создаётся linq-запрос.
А выполнение этого запроса выполняется при вызове метода Count()
в этой строке:
Console.WriteLine(res1.Count() + " " + sw.Elapsed);
Это так называемые lazy evaluation (ленивое вычисление) и deferred execution (отложенное выполнение).
Увеличьте количество итераций (размер списка) на порядок в самом первом цикле:
for (int i = 0; i < 100000; i++)
и это сразу станет заметно на глаз. Паузы между итерациями будут по много секунд, а замеренное время sw.Elapsed
- доли миллисекунд.
Добавлю ещё вот что. Всё сказанное относится к Linq To Objects - запросам поверх IEnumerable
.
Но есть ещё и Linq To Sql (Entity Framework и т. п.) - запросы поверх IQueryable
. Они конструируются на клиенте, а выполняются на сервере. И вот тут уже действуют другие правила. План запросов может кэшироваться на сервере и прочее.
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Набираю в командной строке net use - показывает все сетевые дискиВ моем c# коде - пусто
Всем доброго времени суток! Реализовал загрузку изображений в БД и вывод их во вью, но никак не могу решить проблемуПри выгрузке titla'a к изображениям,...
Выдает ошибку что входная строка имела не верный форматЕсли ячейка будет пустой или заполнена будет символами то постоянно выдает эту ошибку
Как можно определить на C#, что круг не заштрихован,а полигон - да? Не нахожу подходящие методы