Подтвердить что ты не робот

Почему OrderBy возвращает IOrderedEnumerable <T> намного быстрее, чем Sort?

Это продолжение этого замечательного вопроса С# Сортировка и сравнение OrderBy. Я буду использовать тот же пример:

List<Person> persons = new List<Person>();
persons.Add(new Person("P005", "Janson"));
persons.Add(new Person("P002", "Aravind"));
persons.Add(new Person("P007", "Kazhal"));

Используемые методы:

persons.Sort((p1, p2) => string.Compare(p1.Name, p2.Name, true));
//and
persons.OrderBy(n => n.Name);

Позвольте мне начать с того, что я понимаю, что нет никаких существенных различий в производительности для беспокойства. Но я хотел бы знать, почему OrderBy выполняет намного лучше, чем Sort. Я использую ответ, отправленный @phoog в исходном вопросе.

private void button1_Click(object sender, EventArgs e)
{
    IEnumerable<Person> people;

    BenchMark(persons => persons.Sort((p1, p2) => string.Compare(p1.Name, p2.Name, true)));

    BenchMark(persons => people = persons.OrderBy(n => n.Name));
}

private static Random randomSeed = new Random();
public static string RandomString(int size, bool lowerCase)
{
    var sb = new StringBuilder(size);
    int start = (lowerCase) ? 97 : 65;
    for (int i = 0; i < size; i++)
    {
        sb.Append((char)(26 * randomSeed.NextDouble() + start));
    }
    return sb.ToString();
}

private static void BenchMark(Action<List<Person>> action)
{
    List<Person> persons = new List<Person>();
    for (int i = 0; i < 10000; i++)
    {
        persons.Add(new Person("P" + i.ToString(), RandomString(5, true)));
    }
    List<Person> unsortedPersons = new List<Person>(persons);

    Stopwatch watch = new Stopwatch();
    for (int i = 0; i < 100; i++)
    {
        watch.Start();

        action(persons);

        watch.Stop();
        persons.Clear();
        persons.AddRange(unsortedPersons);
    }

    MessageBox.Show(watch.Elapsed.TotalMilliseconds.ToString());
}

Результат:

Sort() => 3500 ~ 5000 ms
OrderBy() => 0.2 ~ 1.5 ms

Хотя различия были глубокими даже при меньших списках, которые я тестировал изначально, он становился все более и более ярким, когда размер коллекции увеличивался. Возможно, мне не хватает какого-то ключа к пониманию коллекций .NET, но я думаю, что с Sort действует на существующий List<T>, он должен иметь меньшие накладные расходы (если каждый из них) при обработке по сравнению с OrderBy, который действует на том же List<T> (в нашем случае persons), но нужно вернуть другую коллекцию IOrderedEnumerable<T>. Но все же OrderBy работает намного лучше. List<T> может иметь определенные накладные расходы по сравнению с типом IEnumerable<T>, но Sort в любом случае действует на существующий список! Кроме того, я немного удивлен, увидев, что метод Linq работает быстрее, чем существующий метод .NET.

Все ответы в исходном вопросе сравнивают Sort с OrderBy.ToList, которые, как я полагаю, будут иметь некоторые накладные расходы и, следовательно, будут работать более или менее одинаково.

Каковы могут быть различия в реализации?


Изменить: Хорошо, я узнал что-то новое. Вот как я подтвердил отложенное исполнение.

private void button1_Click(object sender, EventArgs e)
{
    BenchMark(persons =>
    {
        persons.Sort((p1, p2) => string.Compare(p1.Name, p2.Name, true));
        foreach (var item in persons)
        {
            break;
        }
    });

    BenchMark(persons =>
    {
        IEnumerable<Person> people = persons.OrderBy(n => n.Name);
        foreach (var item in people)
        {
            break;
        }
    });
}

Sort работал в 4000 - 5000 мс, а OrderBy работал чуть выше 5000 мс. Так что мой вывод был неправильным. Оба они выполнялись на равных условиях, как только я начал перечислять коллекции. Я предпочитаю синтаксис OrderBy anyday:)

Изменить 2: Я только что нашел, что это точный дубликат этого. Но вот более интересный вопрос об отложенном исполнении в целом, хотя и не о заказе в целом.

4b9b3361

Ответ 1

В этом случае OrderBy выполняется намного быстрее, потому что вы на самом деле не выполняете его.

Пока вы не перечисляете результаты, запрос откладывается, поэтому он никогда не выполняет заказы. Пока вы на самом деле не перечислите результаты, IOrderedEnumerable<T> не обрабатывает входные данные и не выполняет какую-либо форму заказа.

Попробуйте изменить свой тест на:

 BenchMark(persons => people = persons.OrderBy(n => n.Name).Count());

Вызов Count() заставит упорядочение фактически произойти (так как ему нужно перечислить IOrderedEnumerable<T>, чтобы сгенерировать счетчик), что значительно уменьшит ваши тайминги.

Большинство методов расширения LINQ работают таким образом - пока вы не перечислите их (через Count(), вызывая ToList() или просто используя их в обычном цикле foreach и т.д.), они будут иметь незначительное влияние, поскольку они не Фактически ничего не делайте напрямую, кроме создания перечислимого. Причина, по которой другие тесты сравниваются с OrderBy(...).ToList(), заключается в том, что добавление ToList() заставляет OrderBy полностью выполнять и фактически заказывать результаты.

Ответ 2

OrderBy(), как и большинство методов LINQ, использует отложенное выполнение.

Он фактически ничего не делает, пока вы не перечислите его результаты.

Чтобы правильно измерить его производительность, вы можете вызвать .OrderBy(...).Count().

Ответ 3

OrderBy() не создает отсортированный список.

Он создает объект IEnumerable, который при его перечислении генерирует отсортированный список. Фактическая сортировка не выполняется, пока вы не перечислите список.