C # Sort and OrderBy confronto


105

Posso ordinare un elenco utilizzando Sort o OrderBy. Quale è più veloce? Entrambi lavorano sullo stesso algoritmo?

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

1.

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

2.

var query = persons.OrderBy(n => n.Name, new NameComparer());

class NameComparer : IComparer<string>
{
    public int Compare(string x,string y)
    {
      return  string.Compare(x, y, true);
    }
}

22
Non posso credere che nessuna delle risposte lo abbia menzionato, ma la differenza più grande è questa: OrderBy crea una copia ordinata di Array o List, mentre Sort lo ordina effettivamente sul posto.
PRMan

2
come titolo dice il confronto, vorrei aggiungere che OrderBy è stabile e l'ordinamento è stabile fino a 16 elementi poiché viene utilizzato l'ordinamento per inserimento fino a 16 elementi se gli elementi sono più di questo, quindi passa ad altri algoritmi instabili Modifica: stabile significa mantenere l'ordine relativo di elementi aventi la stessa chiave.
Eklavyaa

@PRMan No, OrderBy crea un enumerabile pigro. Solo se chiami un metodo come ToList sull'enumerabile restituito ottieni una copia ordinata.
Stewart

1
@Stewart, non consideri Array.Copy o Collection.Copy in TElement [] in Buffer in System.Core / System / Linq / Enumerable.cs come una copia? E se chiami ToList su IEnumerable, potresti avere momentaneamente 3 copie in memoria contemporaneamente. Questo è un problema per array molto grandi, che era parte del mio punto. Inoltre, se è necessario lo stesso ordine ordinato più di una volta, chiamare Ordina sul posto una volta è molto più efficiente rispetto all'ordinamento ripetuto dell'elenco, a causa della sua permanenza.
PRMan

1
@PRMan Oh, volevi dire che una copia ordinata viene creata internamente. Tuttavia è impreciso, poiché OrderBy non crea la copia: da quello che posso vedere, questo viene fatto dal metodo GetEnumerator quando inizi effettivamente a scorrere la raccolta. Ho appena provato a scorrere il mio codice e ho scoperto che il codice che popola una variabile da un'espressione LINQ viene eseguito quasi istantaneamente, ma quando si entra nel ciclo foreach si impiega tempo a ordinarlo. Immagino che quando avrò un po 'più di tempo dovrei dedicare un po' a cercare di capire come funziona dietro le quinte.
Stewart

Risposte:


90

Perché non misurarlo:

class Program
{
    class NameComparer : IComparer<string>
    {
        public int Compare(string x, string y)
        {
            return string.Compare(x, y, true);
        }
    }

    class Person
    {
        public Person(string id, string name)
        {
            Id = id;
            Name = name;
        }
        public string Id { get; set; }
        public string Name { get; set; }
    }

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

        Sort(persons);
        OrderBy(persons);

        const int COUNT = 1000000;
        Stopwatch watch = Stopwatch.StartNew();
        for (int i = 0; i < COUNT; i++)
        {
            Sort(persons);
        }
        watch.Stop();
        Console.WriteLine("Sort: {0}ms", watch.ElapsedMilliseconds);

        watch = Stopwatch.StartNew();
        for (int i = 0; i < COUNT; i++)
        {
            OrderBy(persons);
        }
        watch.Stop();
        Console.WriteLine("OrderBy: {0}ms", watch.ElapsedMilliseconds);
    }

    static void Sort(List<Person> list)
    {
        list.Sort((p1, p2) => string.Compare(p1.Name, p2.Name, true));
    }

    static void OrderBy(List<Person> list)
    {
        var result = list.OrderBy(n => n.Name, new NameComparer()).ToArray();
    }
}

Sul mio computer, quando viene compilato in modalità di rilascio, questo programma stampa:

Sort: 1162ms
OrderBy: 1269ms

AGGIORNARE:

Come suggerito da @Stefan, ecco i risultati dell'ordinamento di un grande elenco meno volte:

List<Person> persons = new List<Person>();
for (int i = 0; i < 100000; i++)
{
    persons.Add(new Person("P" + i.ToString(), "Janson" + i.ToString()));
}

Sort(persons);
OrderBy(persons);

const int COUNT = 30;
Stopwatch watch = Stopwatch.StartNew();
for (int i = 0; i < COUNT; i++)
{
    Sort(persons);
}
watch.Stop();
Console.WriteLine("Sort: {0}ms", watch.ElapsedMilliseconds);

watch = Stopwatch.StartNew();
for (int i = 0; i < COUNT; i++)
{
    OrderBy(persons);
}
watch.Stop();
Console.WriteLine("OrderBy: {0}ms", watch.ElapsedMilliseconds);

stampe:

Sort: 8965ms
OrderBy: 8460ms

In questo scenario sembra che OrderBy funzioni meglio.


UPDATE2:

E usando nomi casuali:

List<Person> persons = new List<Person>();
for (int i = 0; i < 100000; i++)
{
    persons.Add(new Person("P" + i.ToString(), RandomString(5, true)));
}

Dove:

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();
}

I rendimenti:

Sort: 8968ms
OrderBy: 8728ms

Still OrderBy è più veloce


2
Penso che sia molto diverso dall'ordinamento di un elenco molto piccolo (3 elementi) 1000000 volte o dall'ordinamento di un elenco molto grande (1000000 elementi) solo poche volte. Entrambi sono molto rilevanti. In pratica, la dimensione media dell'elenco (cosa è medio? ... diciamo 1000 elementi per ora) è molto interessante. IMHO, ordinare elenchi con 3 elementi non è molto significativo.
Stefan Steinegger

25
Notare che c'è una differenza tra "più veloce" e "notevolmente più veloce". Nel tuo ultimo esempio la differenza era di circa un quarto di secondo. L'utente se ne accorgerà? È inaccettabile che l'utente attenda quasi nove secondi per il risultato? Se le risposte a entrambe le domande sono "no", non importa quale scegli dal punto di vista delle prestazioni.
Eric Lippert

12
Nota anche che il test qui ordina l'elenco prima di avviare il cronometro, quindi stiamo confrontando il modo in cui i due algoritmi si confrontano di fronte a input ordinati. Questo può essere molto diverso dalle loro prestazioni relative con input non ordinato.
phoog

3
Questi risultati sono piuttosto sorprendenti IMHO, considerando il fatto che LINQdeve spendere memoria aggiuntiva rispetto a List<T>.Sortun'implementazione sul posto . Non sono sicuro che abbiano migliorato questo aspetto nelle versioni .NET più recenti, ma sulla mia macchina (versione i7 di terza generazione .NET 4.5 a 64 bit) ha Sortprestazioni migliori OrderByin tutti i casi. Inoltre, guardando OrderedEnumerable<T>il codice sorgente, sembra che crei tre array aggiuntivi (prima a Buffer<T>, poi un array di chiavi proiettate, quindi un array di indici) prima di chiamare infine Quicksort per ordinare l'array di indici in posizione.
Groo

2
... e poi, dopo tutto questo, c'è la ToArraychiamata che crea l'array risultante. Le operazioni di memoria e l'indicizzazione degli array sono operazioni incredibilmente veloci, ma non riesco ancora a trovare la logica alla base di questi risultati.
Groo

121

No, non sono lo stesso algoritmo. Per i principianti, LINQ OrderByè documentato come stabile (cioè se due elementi hanno lo stesso Name, appariranno nell'ordine originale).

Dipende anche dal fatto che si bufferizzi la query o la si iteri più volte (LINQ-to-Objects, a meno che non si memorizzi nel buffer il risultato, riordinerà per foreach).

Per la OrderByquery, sarei anche tentato di utilizzare:

OrderBy(n => n.Name, StringComparer.{yourchoice}IgnoreCase);

(per {yourchoice}uno di CurrentCulture, Ordinalo InvariantCulture).

List<T>.Sort

Questo metodo utilizza Array.Sort, che utilizza l'algoritmo QuickSort. Questa implementazione esegue un ordinamento instabile; cioè, se due elementi sono uguali, il loro ordine potrebbe non essere mantenuto. Al contrario, un ordinamento stabile conserva l'ordine degli elementi che sono uguali.

Enumerable.OrderBy

Questo metodo esegue un ordinamento stabile; ovvero, se le chiavi di due elementi sono uguali, l'ordine degli elementi viene mantenuto. Al contrario, un ordinamento instabile non conserva l'ordine degli elementi che hanno la stessa chiave. ordinare; cioè, se due elementi sono uguali, il loro ordine potrebbe non essere mantenuto. Al contrario, un ordinamento stabile conserva l'ordine degli elementi che sono uguali.


5
Se usi .NET Reflector o ILSpy per aprire Enumerable.OrderBye approfondire la sua implementazione interna, puoi vedere che l'algoritmo di ordinamento OrderBy è una variante di QuickSort che esegue un ordinamento stabile. (Vedi System.Linq.EnumerableSorter<TElement>.) Quindi, Array.Sorte ci si Enumerable.OrderBypuò aspettare che entrambi abbiano tempi di esecuzione O (N log N) , dove N è il numero di elementi nella raccolta.
John Beyer

@ Marc Non seguo esattamente quale sarebbe la differenza se due elementi fossero uguali e il loro ordine non fosse preservato. Questo certamente non sembra un problema per i tipi di dati primitivi. Ma anche per un tipo di riferimento, perché sarebbe importante, se dovessi ordinare, una persona con il nome Marc Gravell è apparsa prima di un'altra persona con il nome Marc Gravell (per esempio :))? Non sto mettendo in dubbio la tua risposta / conoscenza, piuttosto cerco un'applicazione di questo scenario.
Mukus

4
@ Mukus immagina di ordinare la rubrica di una società per nome (o addirittura per data di nascita): inevitabilmente ci saranno dei duplicati. La domanda in definitiva è: cosa succede per loro? Il sotto-ordine è definito?
Marc Gravell

55

La risposta di Darin Dimitrov mostra che OrderByè leggermente più veloce di List.Sortquando si trova di fronte a input già ordinati. Ho modificato il suo codice in modo da ordinare ripetutamente i dati non ordinati e OrderBynella maggior parte dei casi è leggermente più lento.

Inoltre, il OrderBytest utilizza ToArrayper forzare l'enumerazione dell'enumeratore Linq, ma questo ovviamente restituisce un type ( Person[]) che è diverso dall'input type ( List<Person>). Ho quindi rieseguito il test utilizzando ToListinvece di ToArraye ho ottenuto una differenza ancora maggiore:

Sort: 25175ms
OrderBy: 30259ms
OrderByWithToList: 31458ms

Il codice:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

class Program
{
    class NameComparer : IComparer<string>
    {
        public int Compare(string x, string y)
        {
            return string.Compare(x, y, true);
        }
    }

    class Person
    {
        public Person(string id, string name)
        {
            Id = id;
            Name = name;
        }
        public string Id { get; set; }
        public string Name { get; set; }
        public override string ToString()
        {
            return Id + ": " + 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 class PersonList : List<Person>
    {
        public PersonList(IEnumerable<Person> persons)
           : base(persons)
        {
        }

        public PersonList()
        {
        }

        public override string ToString()
        {
            var names = Math.Min(Count, 5);
            var builder = new StringBuilder();
            for (var i = 0; i < names; i++)
                builder.Append(this[i]).Append(", ");
            return builder.ToString();
        }
    }

    static void Main()
    {
        var persons = new PersonList();
        for (int i = 0; i < 100000; i++)
        {
            persons.Add(new Person("P" + i.ToString(), RandomString(5, true)));
        } 

        var unsortedPersons = new PersonList(persons);

        const int COUNT = 30;
        Stopwatch watch = new Stopwatch();
        for (int i = 0; i < COUNT; i++)
        {
            watch.Start();
            Sort(persons);
            watch.Stop();
            persons.Clear();
            persons.AddRange(unsortedPersons);
        }
        Console.WriteLine("Sort: {0}ms", watch.ElapsedMilliseconds);

        watch = new Stopwatch();
        for (int i = 0; i < COUNT; i++)
        {
            watch.Start();
            OrderBy(persons);
            watch.Stop();
            persons.Clear();
            persons.AddRange(unsortedPersons);
        }
        Console.WriteLine("OrderBy: {0}ms", watch.ElapsedMilliseconds);

        watch = new Stopwatch();
        for (int i = 0; i < COUNT; i++)
        {
            watch.Start();
            OrderByWithToList(persons);
            watch.Stop();
            persons.Clear();
            persons.AddRange(unsortedPersons);
        }
        Console.WriteLine("OrderByWithToList: {0}ms", watch.ElapsedMilliseconds);
    }

    static void Sort(List<Person> list)
    {
        list.Sort((p1, p2) => string.Compare(p1.Name, p2.Name, true));
    }

    static void OrderBy(List<Person> list)
    {
        var result = list.OrderBy(n => n.Name, new NameComparer()).ToArray();
    }

    static void OrderByWithToList(List<Person> list)
    {
        var result = list.OrderBy(n => n.Name, new NameComparer()).ToList();
    }
}

2
Ora eseguo il codice di prova in LinqPad 5 (.net 5) e OrderByWithToListimpiega lo stesso tempo di OrderBy.
dovid

38

Penso che sia importante notare un'altra differenza tra Sorte OrderBy:

Supponiamo che esista un Person.CalculateSalary()metodo, che richiede molto tempo; forse più dell'operazione di smistamento di un elenco di grandi dimensioni.

Confrontare

// Option 1
persons.Sort((p1, p2) => Compare(p1.CalculateSalary(), p2.CalculateSalary()));
// Option 2
var query = persons.OrderBy(p => p.CalculateSalary()); 

L'opzione 2 può avere prestazioni superiori, perché chiama il CalculateSalarymetodo solo n volte, mentre l' Sortopzione potrebbe chiamare CalculateSalaryfino a 2 n log ( n ) volte, a seconda del successo dell'algoritmo di ordinamento.


4
Questo è vero, sebbene esista una soluzione a questo problema, ovvero mantenere i dati in un array e utilizzare l'overload Array.Sort che accetta due array, uno di chiavi e l'altro di valori. Nel riempire l'array di chiavi, chiamerai CalculateSalary nvolte. Questo ovviamente non è così conveniente come usare OrderBy.
phoog

14

In poche parole:

Ordinamento elenco / array ():

  • Ordinamento instabile.
  • Fatto sul posto.
  • Usa Introsort / Quicksort.
  • Il confronto personalizzato viene eseguito fornendo un comparatore. Se il confronto è costoso, potrebbe essere più lento di OrderBy () (che consente di utilizzare le chiavi, vedi sotto).

OrderBy / ThenBy ():

  • Ordinamento stabile.
  • Non sul posto.
  • Usa Quicksort. Quicksort non è un tipo stabile. Ecco il trucco: durante l'ordinamento, se due elementi hanno la stessa chiave, confronta il loro ordine iniziale (che è stato memorizzato prima dell'ordinamento).
  • Permette di usare le chiavi (usando lambda) per ordinare gli elementi sui loro valori (es:) x => x.Id. Tutte le chiavi vengono estratte prima dell'ordinamento. Ciò potrebbe comportare prestazioni migliori rispetto all'utilizzo di Sort () e di un comparatore personalizzato.

Fonti: MDSN , sorgente di riferimento e repository dotnet / coreclr (GitHub).

Alcune delle affermazioni sopra elencate si basano sull'attuale implementazione del framework .NET (4.7.2). Potrebbe cambiare in futuro.


0

dovresti calcolare la complessità degli algoritmi usati dai metodi OrderBy e Sort. QuickSort ha una complessità di n (log n) come ricordo, dove n è la lunghezza dell'array.

Ho cercato anche orderby, ma non sono riuscito a trovare alcuna informazione nemmeno nella libreria msdn. se non si hanno gli stessi valori e l'ordinamento relativo a una sola proprietà, preferisco utilizzare il metodo Sort (); in caso contrario, utilizzare OrderBy.


1
Secondo l'attuale documentazione MSDN, Sort utilizza 3 diversi algoritmi di ordinamento basati sull'input. Tra cui QuickSort. La domanda sull'algoritmo OrderBy () è qui (Quicksort): stackoverflow.com/questions/2792074/…
Thor

-1

Voglio solo aggiungere che orderby è molto più utile.

Perché? Perché posso farlo:

Dim thisAccountBalances = account.DictOfBalances.Values.ToList
thisAccountBalances.ForEach(Sub(x) x.computeBalanceOtherFactors())
thisAccountBalances=thisAccountBalances.OrderBy(Function(x) x.TotalBalance).tolist
listOfBalances.AddRange(thisAccountBalances)

Perché comparatore complicato? Basta ordinare in base a un campo. Qui sto ordinando in base a TotalBalance.

Molto facile.

Non posso farlo con l'ordinamento. Mi chiedo perché. Va bene con orderBy.

Per quanto riguarda la velocità è sempre O (n).


3
Domanda: L'O (n) Time (presumo) nella tua risposta si riferisce a OrderBy o Comparer? Non credo che l'ordinamento rapido possa raggiungere O (N) tempo.
Kevman
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.