При профилировании одного из наших приложений мы обнаружили загадочное замедление в некотором коде, когда мы Enumerable.Single(source, predicate)
для большой коллекции, в которой было несколько элементов, которые соответствовали предикату в начале коллекции.
Расследование показало, что реализация Enumerable.Single()
выглядит следующим образом:
public static TSource Single<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
TSource result = default(TSource);
long count = 0;
// Note how this always iterates through ALL the elements:
foreach (TSource element in source) {
if (predicate(element)) {
result = element;
checked { count++; }
}
}
switch (count) {
case 0: throw Error.NoMatch();
case 1: return result;
}
throw Error.MoreThanOneMatch();
}
Эта реализация будет проходить через каждый элемент последовательности, даже если более одного элемента уже соответствует предикату.
Следующая реализация, похоже, даст те же результаты:
public static TSource Single<TSource>(this IEnumerable<TSource> source, Func<TSource, bool> predicate)
{
TSource result = default(TSource);
long count = 0;
foreach (TSource element in source) {
if (predicate(element)) {
if (count == 1) // Exit loop immediately if more than one match found.
throw Error.MoreThanOneMatch();
result = element;
count++; // "checked" is no longer needed.
}
}
if (count == 0)
throw Error.NoMatch();
return result;
}
Кто-нибудь знает, почему фактическая реализация не использует эту очевидную оптимизацию? Я что-то упускаю? (Я не могу себе представить, что такая очевидная оптимизация будет упущена из виду, и поэтому должна быть какая-то конкретная причина для этого.)
(Примечание: я понимаю, что этот вопрос может привлечь ответы, которые являются мнениями; я надеюсь на ответы, которые дают конкретные причины для повторения всех элементов. Если ответ на самом деле "потому что дизайнеры не думали, что такая оптимизация была необходима", тогда этот вопрос не подлежит обсуждению, и я думаю, я должен просто удалить его...)
Для сравнения посмотрите на реализацию Single()
которая не принимает предикат:
public static TSource Single<TSource>(this IEnumerable<TSource> source)
{
IList<TSource> list = source as IList<TSource>;
if (list != null) {
switch (list.Count) {
case 0: throw Error.NoElements();
case 1: return list[0];
}
}
else {
using (IEnumerator<TSource> e = source.GetEnumerator()) {
if (!e.MoveNext()) throw Error.NoElements();
TSource result = e.Current;
if (!e.MoveNext()) return result;
}
}
throw Error.MoreThanOneElement();
}
В этом случае они приложили усилия для добавления оптимизации для IList
.