Почему LINQ .Where(предикат).First() быстрее, чем.First (предикат)?
Я делаю некоторые тесты производительности и заметил, что выражение LINQ, как
result = list.First(f => f.Id == i).Property
медленнее, чем
result = list.Where(f => f.Id == i).First().Property
Это кажется противоречивым. Я бы подумал, что первое выражение будет быстрее, потому что оно может прекратить итерации по списку, как только предикат будет удовлетворен, тогда как я бы подумал, что .Where()
выражение может перебирать весь список перед вызовом .First()
на результирующем подмножестве. Даже если последний делает короткое замыкание, оно не должно быть быстрее, чем использование First напрямую, но это так.
Ниже приведены два действительно простых модульных теста, которые иллюстрируют это. При компиляции с оптимизацией в TestWhereAndFirst примерно на 30% быстрее, чем TestFirstOnly в.Net и Silverlight 4. Я пытался заставить предикат возвращать больше результатов, но разница в производительности такая же.
Кто-нибудь может объяснить, почему .First(fn)
медленнее, чем .Where(fn).First()
? Я вижу аналогичный интуитивно понятный результат с .Count(fn)
по сравнению с .Where(fn).Count()
,
private const int Range = 50000;
private class Simple
{
public int Id { get; set; }
public int Value { get; set; }
}
[TestMethod()]
public void TestFirstOnly()
{
List<Simple> list = new List<Simple>(Range);
for (int i = Range - 1; i >= 0; --i)
{
list.Add(new Simple { Id = i, Value = 10 });
}
int result = 0;
for (int i = 0; i < Range; ++i)
{
result += list.First(f => f.Id == i).Value;
}
Assert.IsTrue(result > 0);
}
[TestMethod()]
public void TestWhereAndFirst()
{
List<Simple> list = new List<Simple>(Range);
for (int i = Range - 1; i >= 0; --i)
{
list.Add(new Simple { Id = i, Value = 10 });
}
int result = 0;
for (int i = 0; i < Range; ++i)
{
result += list.Where(f => f.Id == i).First().Value;
}
Assert.IsTrue(result > 0);
}
1 ответ
Я получил те же результаты: где + первый был быстрее, чем первый.
Как отметил Джон, Linq использует ленивую оценку, поэтому производительность должна быть (и является) в целом одинаковой для обоих методов.
Посмотрев в Reflector, First использует простой цикл foreach для итерации по коллекции, но где имеется множество итераторов, предназначенных для разных типов коллекций (массивов, списков и т. Д.). Предположительно это то, что дает Где небольшое преимущество.