Производительность Skip (и аналогичные функции, как Take)

Я только что посмотрел на исходный код /SkipTake методы расширения .NET Framework (наIEnumerable типа) и обнаружил, что внутренняя реализация работает сGetEnumerator метод:

// .NET framework
    public static IEnumerable Skip(this IEnumerable source, int count)  
    {
        if (source == null) throw Error.ArgumentNull("source"); 
        return SkipIterator(source, count); 
    }

    static IEnumerable SkipIterator(IEnumerable source, int count) 
    {
        using (IEnumerator e = source.GetEnumerator()) 
        {
            while (count > 0 && e.MoveNext()) count--;
            if (count 
 RobSiklos15 нояб. 2013 г., 15:19
Я обнаружил, что этоВсегда лучше предположить, что эти методы никогда не оптимизируются. Даже для Count (), он оптимизирует дляICollection, но нетIReadOnlyCollection, Если вам нужно оптимизировать его, напишите свой.
 Tim S.15 нояб. 2013 г., 15:27
Потому что они никогда не удосужились добавить эту оптимизацию? Я неЯ не вижу каких-либо проблем, когда вы делаете это самостоятельно, если вы обнаружите, что это помогает. Но учтите, что тогдаmyList.Select(..).Skip(100) медленнее, чемmyList.Skip(100).Select(..)хотя онифункционально то же самое.
 D Stanley15 нояб. 2013 г., 15:43
Также обратите внимание, что в Linq-To-SQL и EFSkip а такжеTake подталкиваются к запросу SQL, поэтому он не выполняет итерацию по предыдущим элементам. (SQL может с помощью таблицы / индекса сканирования, но Linq не)
 Bidou15 нояб. 2013 г., 15:45
В этом случае вы называете /SkipTake метод наIQueryable (и неIEnumerable), которая имеет другую реализацию ...

Ответы на вопрос(3)

InvalidOperationException "Коллекция была модифицирована ... » когда основная коллекция тем временем изменяется в другом потоке. Ваша версия несделать это. Это даст ужасные результаты.

Это стандартная практика, которой MSFT следует во всей среде .Net во всех коллекциях, которая не является поточно-ориентированной (хотя некоторые являются исключительными).

Решение Вопроса

Linqон обсуждает (кратко) тот самый вопрос:

Хотя большинство этих операций можетДля разумной оптимизации имеет смысл оптимизировать Skip, если в исходном коде реализован IList. Мы можем пропустить, так сказать, пропуск и сразу перейти к соответствующему указателю. Это быВыяснить случай, когда источник был изменен между итерациями, что может быть одной из причин его 'насколько я не реализован в рамкахЯ в курсе.

Кажется, это разумная причина, чтобы воздержаться от этой оптимизации, но я согласен с тем, что для конкретных случаев может быть целесообразно провести такую оптимизацию, если вы можете гарантировать, что ваш источник сможет ».т / выиграл»не может быть изменено.

 glopes07 сент. 2014 г., 17:38
Для функциональных целей IList представляет собой кешированную коллекцию. Изменение IList между итерациями перечислителя обычно вызывает исключения в любом случае, поэтому я неНе понимаю, почему не следует проводить оптимизацию. Фактически, если вы принимаете возможность случайных параллельных побочных эффектов, изменяющих список, ваши результаты будут случайными, независимо от того, пропустили ли вы напрямую или нет.
 Alezis21 июл. 2016 г., 13:49
У меня была задача оптимизировать некоторый код, выполнение которого занимает часы / дни. Когда я запустил Visual Studio Profiler, я нашел несколько мест для оптимизации, но профилировщик не сталраскрыть проблему пропуска / принятия. Когда я переключился на индексы и удалил Skip / Take, я увеличил производительность до 1700 раз: код был выполнен ~ 9,5 часов, теперь он работал только 20 секунд, так чтоИспользуйте Skip / Take для IList, если вам нужна хорошая производительность.
 Bidou15 нояб. 2013 г., 18:17
Хорошо, я вижу смысл ... но они могли бы использоватьIReadOnlyList в этом случае ... я думаю, что этот интерфейс просто недостаточно используется (и, следовательно, стоимость тестирования, еслиIEnumerable являетсяIReadOnlyList слишком высоко)
 Pinski12 сент. 2017 г., 00:47
Новый дом для Джона Скитасообщение в блоге:codeblog.jonskeet.uk/2011/01/02/...
 Sven Grosen15 нояб. 2013 г., 18:43
@Bidou, эточто яя тоже догадываюсь Для всеобъемлющегоSkip() реализация, проверка этого малоиспользуемого интерфейса могла бы считаться не стоящей.

переопределено LINQон упомянул, что такая оптимизация, как вашаSkip "Wouldn»заметить случай, когда источник был изменен между итерациями ", Вы можете изменить свой код на следующий, чтобы проверить его в этом случае. Это делается путем вызоваMoveNext() на коллекцииперечислитель, хотя это не такт использоватьe.Current, так что метод сгенерирует, если коллекция изменится.

Конечно, это устраняет значительную часть оптимизации: необходимо, чтобы счетчик был создан, частично пройден и утилизирован, но он по-прежнему имеет то преимущество, которое вы не делаете.Бессмысленно шагать через первыйcount объекты. И это может сбить с толку, что у вас естьe.Current это не полезно, так как это указывает наlist[i - count] вместо .list[i]

public static IEnumerable<t> Skip<t>(this IEnumerable<t> source, int count)
{
    using (IEnumerator<t> e = source.GetEnumerator())
    {
        if (source is IList<t>)
        {
            IList<t> list = (IList<t>)source;
            for (int i = count; i < list.Count; i++)
            {
                e.MoveNext();
                yield return list[i];
            }
        }
        else if (source is IList)
        {
            IList list = (IList)source;
            for (int i = count; i < list.Count; i++)
            {
                e.MoveNext();
                yield return (T)list[i];
            }
        }
        else
        {
            // .NET framework
            while (count > 0 && e.MoveNext()) count--;
            if (count <= 0)
            {
                while (e.MoveNext()) yield return e.Current;
            }
        }
    }
}
</t></t></t></t></t></t></t>

Ваш ответ на вопрос