Вопрос по ienumerable, skip-take, linq, performance, c# – Производительность Skip (и аналогичные функции, как Take)

19

Я только что посмотрел на исходный код /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 
Я обнаружил, что этоВсегда лучше предположить, что эти методы никогда не оптимизируются. Даже для Count (), он оптимизирует дляICollection<>, но нетIReadOnlyCollection<>, Если вам нужно оптимизировать его, напишите свой. RobSiklos
Потому что они никогда не удосужились добавить эту оптимизацию? Я неЯ не вижу каких-либо проблем, когда вы делаете это самостоятельно, если вы обнаружите, что это помогает. Но учтите, что тогдаmyList.Select(..).Skip(100) медленнее, чемmyList.Skip(100).Select(..)хотя онифункционально то же самое. Tim S.
Также обратите внимание, что в Linq-To-SQL и EFSkip а такжеTake подталкиваются к запросу SQL, поэтому он не выполняет итерацию по предыдущим элементам. (SQL может с помощью таблицы / индекса сканирования, но Linq не) D Stanley
В этом случае вы называете /SkipTake метод наIQueryable (и неIEnumerable), которая имеет другую реализацию ... Bidou

Ваш Ответ

3   ответа
12

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

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

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

У меня была задача оптимизировать некоторый код, выполнение которого занимает часы / дни. Когда я запустил Visual Studio Profiler, я нашел несколько мест для оптимизации, но профилировщик не сталраскрыть проблему пропуска / принятия. Когда я переключился на индексы и удалил Skip / Take, я увеличил производительность до 1700 раз: код был выполнен ~ 9,5 часов, теперь он работал только 20 секунд, так чтоИспользуйте Skip / Take для IList, если вам нужна хорошая производительность. Alezis
Хорошо, я вижу смысл ... но они могли бы использоватьIReadOnlyList в этом случае ... я думаю, что этот интерфейс просто недостаточно используется (и, следовательно, стоимость тестирования, еслиIEnumerable являетсяIReadOnlyList слишком высоко) Bidou
Для функциональных целей IList представляет собой кешированную коллекцию. Изменение IList между итерациями перечислителя обычно вызывает исключения в любом случае, поэтому я неНе понимаю, почему не следует проводить оптимизацию. Фактически, если вы принимаете возможность случайных параллельных побочных эффектов, изменяющих список, ваши результаты будут случайными, независимо от того, пропустили ли вы напрямую или нет. glopes
@Bidou, эточто яя тоже догадываюсь Для всеобъемлющегоSkip() реализация, проверка этого малоиспользуемого интерфейса могла бы считаться не стоящей. Sven Grosen
Новый дом для Джона Скитасообщение в блоге:codeblog.jonskeet.uk/2011/01/02/... Pinski
1

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

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

2

переопределено 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>

Похожие вопросы