Apakah urutan fungsi LINQ penting?


114

Pada dasarnya, seperti yang dinyatakan dalam pertanyaan ... apakah urutan fungsi LINQ penting dalam hal kinerja ? Jelas hasilnya masih harus identik ...

Contoh:

myCollection.OrderBy(item => item.CreatedDate).Where(item => item.Code > 3);
myCollection.Where(item => item.Code > 3).OrderBy(item => item.CreatedDate);

Keduanya memberi saya hasil yang sama, tetapi dalam urutan LINQ yang berbeda. Saya menyadari bahwa menata ulang beberapa item akan menghasilkan hasil yang berbeda, dan saya tidak mengkhawatirkan hal itu. Yang menjadi perhatian utama saya adalah mengetahui apakah, dalam mendapatkan hasil yang sama, pemesanan dapat memengaruhi kinerja. Dan, tidak hanya pada panggilan 2 LINQ yang saya lakukan (OrderBy, Where), tetapi pada panggilan LINQ mana pun.


9
Pertanyaan yang mengagumkan.
Robert S.

Bahkan lebih jelas lagi bahwa pengoptimalan penyedia penting dengan kasus yang lebih bertele-tele seperti var query = myCollection.OrderBy(item => item.Code).Where(item => item.Code == 3);.
Mark Hurd

1
Anda pantas mendapat Up Vote :), pertanyaan menarik. Saya akan mempertimbangkannya ketika saya menulis Linq saya ke Entitas di EF.
GibboK

1
@GibboK: Berhati-hatilah saat mencoba "mengoptimalkan" kueri LINQ Anda (lihat jawaban di bawah). Terkadang Anda tidak benar-benar mengoptimalkan apa pun. Sebaiknya gunakan alat profiler saat mencoba pengoptimalan.
myermian

Jawaban:


147

Ini akan tergantung pada penyedia LINQ yang digunakan. Untuk LINQ ke Objects, itu pasti bisa membuat perbedaan besar . Asumsikan kita benar-benar punya:

var query = myCollection.OrderBy(item => item.CreatedDate)
                        .Where(item => item.Code > 3);

var result = query.Last();

Itu membutuhkan seluruh koleksi untuk diurutkan dan kemudian difilter. Jika kita memiliki sejuta item, hanya satu yang memiliki kode lebih dari 3, kita akan membuang banyak waktu untuk memesan hasil yang akan dibuang.

Bandingkan dengan operasi terbalik, dengan memfilter terlebih dahulu:

var query = myCollection.Where(item => item.Code > 3)
                        .OrderBy(item => item.CreatedDate);

var result = query.Last();

Kali ini kami hanya mengurutkan hasil yang difilter, yang dalam contoh kasus "hanya satu item yang cocok dengan filter" akan jauh lebih efisien - baik dalam waktu maupun ruang.

Itu juga bisa membuat perbedaan dalam apakah kueri dijalankan dengan benar atau tidak. Mempertimbangkan:

var query = myCollection.Where(item => item.Code != 0)
                        .OrderBy(item => 10 / item.Code);

var result = query.Last();

Tidak apa-apa - kita tahu kita tidak akan pernah membagi dengan 0. Tetapi jika kita melakukan pengurutan sebelum pemfilteran, kueri akan memunculkan pengecualian.


2
@ Jon Skeet, Apakah ada dokumentasi tentang Big-O untuk masing-masing Penyedia dan fungsi LINQ? Atau apakah ini hanya kasus "setiap ekspresi unik untuk situasi tersebut".
michael

1
@ Michael: Ini tidak didokumentasikan dengan sangat jelas, tetapi jika Anda membaca seri blog "Edulinq" saya, saya rasa saya membicarakannya dengan detail yang masuk akal.
Jon Skeet

3
@michael: Anda dapat menemukannya di sini msmvps.com/blogs/jon_skeet/archive/tags/Edulinq/default.aspx
VoodooChild

3
@gdoron: Sejujurnya tidak begitu jelas apa yang Anda maksud. Sepertinya Anda ingin menulis pertanyaan baru. Perlu diingat bahwa Querizable sama sekali tidak mencoba menafsirkan kueri Anda - tugasnya semata - mata untuk menyimpan kueri Anda sehingga sesuatu yang lain dapat menafsirkannya. Perhatikan juga bahwa LINQ ke Objek bahkan tidak menggunakan pohon ekspresi.
Jon Skeet

1
@gdoron: Intinya adalah itu pekerjaan penyedia, bukan pekerjaan Querable. Dan tidak masalah saat menggunakan Entity Framework. Ini tidak masalah untuk LINQ untuk Objects sekalipun. Tapi ya, tentu saja ajukan pertanyaan lain.
Jon Skeet

17

Iya.

Tapi persis apa yang perbedaan kinerja tergantung pada bagaimana pohon ekspresi yang mendasari dievaluasi oleh penyedia LINQ.

Misalnya, kueri Anda mungkin dieksekusi lebih cepat untuk kedua kalinya (dengan klausa WHERE terlebih dahulu) untuk LINQ-to-XML, tetapi lebih cepat pertama kali untuk LINQ-to-SQL.

Untuk mengetahui dengan tepat apa perbedaan kinerja, kemungkinan besar Anda ingin membuat profil aplikasi Anda. Namun, seperti biasa dengan hal-hal seperti itu, pengoptimalan prematur biasanya tidak sepadan dengan usaha - Anda mungkin menemukan masalah selain kinerja LINQ yang lebih penting.


5

Dalam contoh khusus Anda, ini dapat membuat perbedaan pada kinerja.

Kueri pertama: OrderByPanggilan Anda perlu mengulang seluruh urutan sumber, termasuk item yang nilainya Code3 atau kurang. The Whereklausul kemudian juga perlu iterate seluruh memerintahkan urutan.

Kueri kedua: WherePanggilan membatasi urutan hanya untuk item Codeyang lebih besar dari 3. OrderByPanggilan kemudian hanya perlu melintasi urutan yang dikurangi yang dikembalikan oleh Wherepanggilan.


3

Dalam Linq-To-Objects:

Penyortiran agak lambat dan menggunakan O(n)memori. Wheredi sisi lain relatif cepat dan menggunakan memori yang konstan. Jadi mengerjakan Wherelebih dulu akan lebih cepat, dan untuk koleksi besar secara signifikan lebih cepat.

Tekanan memori yang berkurang juga bisa menjadi signifikan, karena alokasi pada tumpukan objek yang besar (bersama dengan koleksinya) relatif mahal menurut pengalaman saya.


1

Jelas hasilnya masih harus identik ...

Perhatikan bahwa ini sebenarnya tidak benar - khususnya, dua baris berikut akan memberikan hasil yang berbeda (untuk sebagian besar penyedia / kumpulan data):

myCollection.OrderBy(o => o).Distinct();
myCollection.Distinct().OrderBy(o => o);

1
Tidak, yang saya maksud adalah bahwa hasilnya harus identik bahkan untuk mempertimbangkan pengoptimalan. Tidak ada gunanya "mengoptimalkan" sesuatu dan mendapatkan hasil yang berbeda.
michael

1

Perlu dicatat bahwa Anda harus berhati-hati saat mempertimbangkan cara mengoptimalkan kueri LINQ. Misalnya, jika Anda menggunakan versi deklaratif LINQ untuk melakukan hal berikut:

public class Record
{
    public string Name { get; set; }
    public double Score1 { get; set; }
    public double Score2 { get; set; }
}


var query = from record in Records
            order by ((record.Score1 + record.Score2) / 2) descending
            select new
                   {
                       Name = record.Name,
                       Average = ((record.Score1 + record.Score2) / 2)
                   };

Jika, karena alasan apa pun, Anda memutuskan untuk "mengoptimalkan" kueri dengan menyimpan rata-rata ke dalam variabel terlebih dahulu, Anda tidak akan mendapatkan hasil yang diinginkan:

// The following two queries actually takes up more space and are slower
var query = from record in Records
            let average = ((record.Score1 + record.Score2) / 2)
            order by average descending
            select new
                   {
                       Name = record.Name,
                       Average = average
                   };

var query = from record in Records
            let average = ((record.Score1 + record.Score2) / 2)
            select new
                   {
                       Name = record.Name,
                       Average = average
                   }
            order by average descending;

Saya tahu tidak banyak orang menggunakan LINQ deklaratif untuk objek, tetapi ini adalah bahan pemikiran yang baik.


0

Itu tergantung pada relevansinya. Misalkan jika Anda memiliki sangat sedikit item dengan Kode = 3, maka pesanan berikutnya akan mengerjakan sejumlah kecil koleksi untuk mendapatkan pesanan berdasarkan tanggal.

Sedangkan jika Anda memiliki banyak item dengan CreatedDate yang sama, maka pesanan berikutnya akan bekerja pada kumpulan koleksi yang lebih besar untuk mendapatkan pesanan berdasarkan tanggal.

Jadi, dalam kedua kasus tersebut akan ada perbedaan kinerja

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.