"Закрытие по переменной дает немного худшую производительность". Как?
Отвечая на вопрос SO, мне сказали, что мое решение представит закрытие переменной, поэтому оно будет иметь немного худшую производительность. Поэтому мой вопрос:
- Как будет закрытие?
- Как это повлияет на производительность?
Вот question
List.Where(s => s.ValidDate.Date == DateTime.Today.Year).ToList();
Вот мое решение . Я ввел переменную yr
для хранения года.
int yr = DateTime.Now.Year;
List.Where(s => s.ValidDate.Year == yr).ToList();
Вот он в ответе .
Ответы
Ответ 1
Прежде всего, эти два решения не являются функционально эквивалентными (если вы исправили сравнение даты с int (.Date == .Today.Year
)):
-
Первый фрагмент переопределяет DateTime.Today.Year
для каждого значения списка, который может давать разные результаты, когда текущий год изменяется во время итерации
-
Второй фрагмент хранит текущий год и повторно использует его, поэтому все элементы в результирующем списке будут иметь тот же год. (Я бы лично принял такой подход, поскольку я хочу убедиться, что результат является нормальным).
Закрытие введено, поскольку лямбда обращается к переменной из ее внешней области, она закрывается над значением yr
. Компилятор С# будет генерировать новый класс с полем, содержащим yr
. Все ссылки на yr
будут заменены новым полем, а исходный yr
даже не будет существовать в скомпилированном коде
Я сомневаюсь, что будет штраф за выполнение, введя закрытие. Если есть, код с использованием закрытия будет быстрее, так как ему не нужно создавать новые экземпляры DateTime
для каждого элемента списка, а затем разыменовывать два свойства. Он должен иметь доступ только к полю класса, сгенерированного компилятором, который содержит значение int текущего года. (Любой, кто хочет сравнить сгенерированный код или профиль IL, два фрагмента?:))
Ответ 2
В дополнение к ответу knittl я хотел попробовать и измерить производительность с закрытием и без него, вот как выглядит мой тест:
internal class SomeData {
public DateTime ValidDate { get; set; }
// other data ...
}
class Program {
static void Main(string[] args) {
var stopWatch = new Stopwatch();
// Test with closure
IEnumerable<SomeData> data1 = CreateTestData(100000);
stopWatch.Start();
int yr = DateTime.Now.Year;
List<SomeData> results1 = data1.Where(x => x.ValidDate.Year == yr).ToList();
stopWatch.Stop();
Console.WriteLine("With a closure - {0} ms", stopWatch.Elapsed.Milliseconds);
// ### Output on my machine (consistently): With a closure - 16 ms
stopWatch.Reset();
// Test without a closure
IEnumerable<SomeData> data2 = CreateTestData(100000);
stopWatch.Start();
List<SomeData> results2 = data2.Where(x => x.ValidDate.Year == DateTime.Today.Year).ToList();
stopWatch.Stop();
Console.WriteLine("Without a closure - {0} ms", stopWatch.Elapsed.Milliseconds);
// ### Output on my machine: Without a closure - 33 ms
}
private static IEnumerable<SomeData> CreateTestData(int numberOfItems) {
var dt = DateTime.Today;
for (int i = 0; i < numberOfItems; i++) {
yield return new SomeData {ValidDate = dt};
}
}
}
Нижняя строка моих тестов - как я ожидал, версия с закрытием значительно быстрее.
Ответ 3
Здесь наивное измерение времени, просто чтобы дополнить ответ knittl.
В результате версия, которая оценивает DateTime.Now
каждый раз, более чем в 10 раз медленнее, чем ваш код.
Результаты на моей машине: T1: 8878 мс; T2: 589 мс. (Максимальная оптимизация, без отладчика и т.д.).
class Program
{
static void Main(string[] args)
{
var things = new List<Something>();
var random = new Random(111);
for (int i = 0; i < 100000; ++i)
{
things.Add(new Something(random.Next(2010, 2016)));
}
// to avoid measuring the JIT compilation and optimization time
T1(things);
T2(things);
var sw = Stopwatch.StartNew();
for (int i = 0; i < 100; ++i)
{
T1(things);
}
Console.WriteLine(sw.ElapsedMilliseconds);
sw.Restart();
for (int i = 0; i < 100; ++i)
{
T2(things);
}
Console.WriteLine(sw.ElapsedMilliseconds);
Console.ReadLine();
}
private static void T1(List<Something> list)
{
var result = list.Where(x => x.ValidDate.Year == DateTime.Now.Year).ToList();
}
private static void T2(List<Something> list)
{
var yr = DateTime.Now.Year;
var result = list.Where(x => x.ValidDate.Year == yr).ToList();
}
}
class Something
{
public Something(int year)
{
this.ValidDate = new DateTime(year, 1, 1);
}
public DateTime ValidDate { get; private set; }
}