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.