C # Sortir dan OrderBy perbandingan


105

Saya dapat mengurutkan daftar menggunakan Sort atau OrderBy. Mana yang lebih cepat? Apakah keduanya bekerja dengan algoritme yang sama?

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
Saya tidak percaya bahwa tidak ada jawaban yang menyebutkan ini, tetapi perbedaan terbesar adalah ini: OrderBy membuat salinan yang diurutkan dari Array atau Daftar, sementara Sortir sebenarnya mengurutkannya di tempat.
PRMan

2
sebagai judul mengatakan perbandingan, saya ingin menambahkan bahwa OrderBy stabil dan sortir stabil hingga 16 elemen karena hingga 16 elemen penyisipan sort digunakan jika elemen lebih dari itu kemudian beralih ke algos tidak stabil lainnya Edit: stable berarti mempertahankan urutan relatif elemen yang memiliki kunci yang sama.
Eklavyaa

@PRMan Tidak, OrderBy membuat penghitung malas. Hanya jika Anda memanggil metode seperti ToList pada enumerable yang dikembalikan, Anda mendapatkan salinan yang diurutkan.
Stewart

1
@Stewart, Anda tidak menganggap Array.Copy atau Collection.Copy ke TElement [] di Buffer di System.Core / System / Linq / Enumerable.cs sebagai salinan? Dan jika Anda memanggil ToList di IEnumerable, untuk sementara Anda dapat memiliki 3 salinan di memori sekaligus. Ini adalah masalah untuk array yang sangat besar, yang merupakan bagian dari poin saya. Selain itu, jika Anda memerlukan urutan terurut yang sama lebih dari sekali, maka memanggil Sortir di tempat sekali jauh lebih efisien daripada berulang kali mengurutkan Daftar, karena sifatnya yang permanen.
PRMan

1
@PRMan Oh, maksud Anda salinan yang diurutkan dibuat secara internal. Tetap saja itu tidak akurat, karena OrderBy tidak membuat salinannya - dari apa yang saya lihat, ini dilakukan dengan metode GetEnumerator ketika Anda benar-benar mulai mengulang koleksi. Saya baru saja mencoba melangkah melalui kode saya, dan menemukan bahwa kode yang mengisi variabel dari ekspresi LINQ berjalan hampir seketika, tetapi ketika Anda masuk ke loop foreach, ia menghabiskan waktu untuk menyortirnya. Saya kira ketika saya memiliki lebih banyak waktu, saya harus meluangkan waktu untuk mencoba mencari tahu cara kerjanya di balik layar.
Stewart

Jawaban:


90

Mengapa tidak mengukurnya:

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

Di komputer saya ketika dikompilasi dalam mode Rilis program ini mencetak:

Sort: 1162ms
OrderBy: 1269ms

MEMPERBARUI:

Seperti yang disarankan oleh @Stefan, berikut adalah hasil dari menyortir daftar besar lebih sedikit:

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

Cetakan:

Sort: 8965ms
OrderBy: 8460ms

Dalam skenario ini, sepertinya OrderBy berkinerja lebih baik.


UPDATE2:

Dan menggunakan nama acak:

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

Dimana:

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

Hasil:

Sort: 8968ms
OrderBy: 8728ms

Masih OrderBy lebih cepat


2
Menurut saya, jauh berbeda dengan mengurutkan daftar yang sangat kecil (3 item) 1000000 kali, atau dengan menyortir daftar yang sangat besar (1000000 item) hanya beberapa kali. Keduanya sangat relevan. Dalam praktiknya, daftar ukuran sedang (sedang apa? ... katakanlah 1000 item untuk saat ini) adalah yang paling menarik. IMHO, menyortir daftar dengan 3 item tidak terlalu berarti.
Stefan Steinegger

25
Perhatikan bahwa ada perbedaan antara "lebih cepat" dan "sangat cepat". Dalam contoh terakhir Anda, perbedaannya sekitar seperempat detik. Apakah pengguna akan menyadarinya? Apakah tidak dapat diterima bagi pengguna untuk menunggu hampir sembilan detik untuk mendapatkan hasilnya? Jika jawaban untuk kedua pertanyaan tersebut adalah "tidak" maka tidak masalah mana yang Anda pilih dari perspektif kinerja.
Eric Lippert

12
Perhatikan juga bahwa pengujian di sini mengurutkan daftar sebelum memulai stopwatch, jadi kami membandingkan bagaimana kedua algoritme dibandingkan saat dihadapkan dengan input yang diurutkan. Ini mungkin sangat berbeda dari kinerja relatifnya dengan input yang tidak diurutkan.
phoog

3
Hasil ini IMHO cukup mengejutkan, mengingat fakta bahwa LINQharus menghabiskan memori tambahan dibandingkan dengan List<T>.Sortimplementasi di tempat . Saya tidak yakin apakah mereka meningkatkan ini di versi .NET yang lebih baru, tetapi pada mesin saya (rilis i7 3rd gen 64-bit .NET 4.5) Sortberkinerja lebih baik OrderBydalam semua kasus. Selanjutnya, dengan melihat OrderedEnumerable<T>kode sumber, tampaknya ia membuat tiga larik tambahan (pertama a Buffer<T>, lalu larik kunci yang diproyeksikan, lalu larik indeks) sebelum akhirnya memanggil Quicksort untuk mengurutkan larik indeks pada tempatnya.
Groo

2
... dan kemudian setelah semua ini, ada ToArraypanggilan yang membuat array yang dihasilkan. Operasi memori dan pengindeksan array adalah operasi yang sangat cepat, tetapi saya masih tidak dapat menemukan logika di balik hasil ini.
Groo

121

Tidak, mereka bukan algoritme yang sama. Sebagai permulaan, LINQ OrderBydidokumentasikan sebagai stabil (yaitu jika dua item memiliki yang samaName , mereka akan muncul dalam urutan aslinya).

Itu juga tergantung pada apakah Anda menyangga kueri vs mengulanginya beberapa kali (LINQ-to-Objects, kecuali Anda menyangga hasilnya, akan mengurutkan ulang per foreach ).

Untuk OrderBykueri, saya juga akan tergoda untuk menggunakan:

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

(untuk {yourchoice}salah satu CurrentCulture, Ordinalatau InvariantCulture).

List<T>.Sort

Metode ini menggunakan Array.Sort, yang menggunakan algoritma QuickSort. Implementasi ini melakukan pengurutan yang tidak stabil; artinya, jika dua elemen sama, urutannya mungkin tidak dipertahankan. Sebaliknya, urutan stabil mempertahankan urutan elemen yang sama.

Enumerable.OrderBy

Metode ini melakukan pengurutan yang stabil; Artinya, jika kunci dari dua elemen sama, urutan elemen dipertahankan. Sebaliknya, pengurutan yang tidak stabil tidak mempertahankan urutan elemen yang memiliki kunci yang sama. menyortir; artinya, jika dua elemen sama, urutannya mungkin tidak dipertahankan. Sebaliknya, urutan stabil mempertahankan urutan elemen yang sama.


5
Jika Anda menggunakan .NET Reflector atau ILSpy untuk membuka Enumerable.OrderBydan menelusuri implementasi internalnya, Anda dapat melihat bahwa algoritme pengurutan OrderBy adalah varian dari QuickSort yang melakukan pengurutan stabil. (Lihat System.Linq.EnumerableSorter<TElement>.) Jadi, Array.Sortdan Enumerable.OrderBykeduanya dapat diharapkan memiliki waktu eksekusi O (N log N) , di mana N adalah jumlah elemen dalam koleksi.
John Beyer

@Marc Saya tidak begitu mengerti apa perbedaannya jika dua elemen sama dan urutannya tidak dipertahankan. Ini tentunya tidak terlihat seperti masalah untuk tipe data primitif. Tetapi bahkan untuk tipe referensi, mengapa penting, jika saya mengurutkan, orang dengan nama Marc Gravell muncul di hadapan orang lain dengan nama Marc Gravell (misalnya :))? Saya tidak mempertanyakan jawaban / pengetahuan Anda, melainkan mencari aplikasi dari skenario ini.
Mukus

4
@Mukus bayangkan Anda mengurutkan buku alamat perusahaan berdasarkan nama (atau memang berdasarkan tanggal lahir) - pasti akan ada duplikat. Pertanyaannya adalah: apa yang terjadi pada mereka? Apakah sub-pesanan sudah ditentukan?
Marc Gravell

55

Jawaban Darin Dimitrov menunjukkan bahwa OrderBysedikit lebih cepat daripada List.Sortsaat dihadapkan dengan input yang sudah diurutkan. Saya memodifikasi kodenya sehingga berulang kali mengurutkan data yang tidak diurutkan, danOrderBy dalam banyak kasus sedikit lebih lambat.

Selanjutnya, OrderBypengujian tersebut digunakan ToArrayuntuk memaksa pencacahan enumerator Linq, tetapi itu jelas mengembalikan type ( Person[]) yang berbeda dari tipe input ( List<Person>). Oleh karena itu, saya menjalankan ulang pengujian menggunakan ToListdaripada ToArraydan mendapatkan perbedaan yang lebih besar:

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

Kode:

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
Saya menjalankan kode tes sekarang di LinqPad 5 (.net 5) dan OrderByWithToListmembutuhkan waktu yang sama seperti OrderBy.
dovid

38

Saya pikir penting untuk mencatat perbedaan lain antara Sortdan OrderBy:

Misalkan ada sebuah Person.CalculateSalary()metode yang membutuhkan banyak waktu; mungkin lebih dari sekadar operasi penyortiran daftar besar.

Membandingkan

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

Opsi 2 mungkin memiliki kinerja yang unggul, karena hanya memanggil CalculateSalarymetode sebanyak n kali, sedangkan Sortopsi tersebut dapat memanggil CalculateSalaryhingga 2 n log ( n ) kali, tergantung pada keberhasilan algoritme pengurutan.


4
Ini benar, meskipun ada solusi untuk masalah itu, yaitu, untuk menyimpan data dalam array dan menggunakan overload Array.Sort yang mengambil dua array, satu kunci dan nilai lainnya. Dalam mengisi key array, Anda akan memanggil nwaktu CalculateSalary . Ini jelas tidak senyaman menggunakan OrderBy.
phoog

14

Singkatnya:

Daftar / Array Sort ():

  • Urutan tidak stabil.
  • Selesai di tempat.
  • Gunakan Introsort / Quicksort.
  • Perbandingan kustom dilakukan dengan menyediakan pembanding. Jika perbandingan mahal, mungkin akan lebih lambat dari OrderBy () (yang memungkinkan untuk menggunakan kunci, lihat di bawah).

OrderBy / ThenBy ():

  • Jenis yang stabil.
  • Tidak di tempat.
  • Gunakan Quicksort. Quicksort bukanlah jenis yang stabil. Inilah triknya: saat menyortir, jika dua elemen memiliki kunci yang sama, ia membandingkan urutan awalnya (yang telah disimpan sebelum menyortir).
  • Memungkinkan untuk menggunakan kunci (menggunakan lambda) untuk mengurutkan elemen pada nilainya (misalnya:) x => x.Id. Semua kunci diekstrak terlebih dahulu sebelum diurutkan. Ini mungkin menghasilkan kinerja yang lebih baik daripada menggunakan Sortir () dan pembanding khusus.

Sumber: MDSN , sumber referensi dan dotnet / coreclr repositori (GitHub).

Beberapa pernyataan yang tercantum di atas didasarkan pada implementasi framework .NET saat ini (4.7.2). Itu mungkin berubah di masa depan.


0

Anda harus menghitung kompleksitas algoritma yang digunakan oleh metode OrderBy dan Sort. QuickSort memiliki kompleksitas n (log n) seperti yang saya ingat, di mana n adalah panjang array.

Saya telah mencari orderby juga, tetapi saya tidak dapat menemukan informasi apapun bahkan di perpustakaan MSDN. jika Anda tidak memiliki nilai yang sama dan pengurutan yang terkait dengan hanya satu properti, saya lebih suka menggunakan metode Sort (); jika tidak, gunakan OrderBy.


1
Menurut dokumentasi MSDN saat ini, Sortir menggunakan 3 algoritme pengurutan berbeda berdasarkan input. Diantaranya adalah QuickSort. Pertanyaan tentang algoritma OrderBy () ada di sini (Quicksort): stackoverflow.com/questions/2792074/…
Thor

-1

Saya hanya ingin menambahkan bahwa orderby jauh lebih berguna.

Mengapa? Karena saya bisa melakukan ini:

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

Mengapa pembanding rumit? Cukup urutkan berdasarkan bidang. Disini saya mengurutkan berdasarkan TotalBalance.

Sangat mudah.

Saya tidak bisa melakukan itu dengan baik. Kenapa ya. Lakukan dengan baik dengan orderBy.

Adapun kecepatan selalu O (n).


3
Pertanyaan: O (n) Waktu (saya asumsikan) dalam jawaban Anda mengacu pada OrderBy atau Comparer? Saya tidak berpikir jenis cepat dapat mencapai waktu O (N).
Kevman
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.