LINQ işlevlerinin sırası önemli mi?


114

Temel olarak, sorunun da ifade ettiği gibi ... LINQ işlevlerinin sırası performans açısından önemli mi? Açıkçası sonuçların yine de aynı olması gerekiyordu ...

Misal:

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

Her ikisi de bana aynı sonuçları döndürüyor, ancak farklı bir LINQ sırasındalar. Bazı öğelerin yeniden sıralanmasının farklı sonuçlara yol açacağının farkındayım ve bunlarla ilgilenmiyorum. Asıl endişem, aynı sonuçları elde ederken, sipariş vermenin performansı etkileyip etkilemeyeceğini bilmek. Ve sadece yaptığım 2 LINQ çağrısında değil (OrderBy, Where), ama herhangi bir LINQ çağrısında.


9
Harika soru.
Robert S.

Sağlayıcının optimizasyonunun daha bilgiçlik gerektiren bir durumla önemli olduğu daha da açıktır var query = myCollection.OrderBy(item => item.Code).Where(item => item.Code == 3);.
Mark Hurd

1
Bir Yukarı Oyu hak ediyorsunuz :), ilginç sorular. Linq'imi EF'teki Varlıklara yazarken bunu dikkate alacağım.
GibboK

1
@GibboK: LINQ sorgularınızı "optimize etmeye" çalışırken dikkatli olun (aşağıdaki yanıta bakın). Bazen gerçekten hiçbir şeyi optimize etmiyorsunuz. Optimizasyona çalışırken bir profil oluşturucu aracı kullanmak en iyisidir.
myermian

Yanıtlar:


147

Kullanımdaki LINQ sağlayıcısına bağlı olacaktır. LINQ to Objects için bu kesinlikle büyük bir fark yaratabilir. Aslında sahip olduğumuzu varsayalım:

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

var result = query.Last();

Bu, tüm koleksiyonun sıralanmasını ve ardından filtrelenmesini gerektirir. Yalnızca birinin kodu 3'ten büyük olan bir milyon öğemiz olsaydı, bir kenara atılacak sonuçları sıralayarak çok fazla zaman harcıyorduk.

Bunu ilk önce filtreleyerek tersine işlemle karşılaştırın:

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

var result = query.Last();

Bu sefer yalnızca filtrelenmiş sonuçları sıralıyoruz, "filtreyle eşleşen yalnızca tek bir öğe" örneğinde, hem zaman hem de mekan açısından çok daha verimli olacaktır.

Ayrıca olabilir sorgu doğru ya da yürütür olup bir fark. Düşünmek:

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

var result = query.Last();

Sorun değil - asla 0'a bölmeyeceğimizi biliyoruz. Ama siparişi daha önce yaparsak filtrelemeden , sorgu bir istisna atar.


2
@Jon Skeet, LINQ Sağlayıcılarının ve işlevlerinin her biri için Big-O'yla ilgili belgeler var mı? Veya bu sadece "her ifade duruma özgüdür" durumudur.
michael

1
@michael: Çok net bir şekilde belgelenmemiş, ancak "Edulinq" blog dizimi okursanız, sanırım ondan makul ayrıntılarla bahsedeceğim.
Jon Skeet


3
@gdoron: Dürüst olmak gerekirse, ne demek istediğin pek açık değil. Görünüşe göre yeni bir soru yazmak isteyebilirsin. Queryable'ın sorgunuzu hiç yorumlamaya çalışmadığını unutmayın - görevi yalnızca sorgunuzu başka bir şeyin yorumlayabilmesi için korumaktır. Ayrıca LINQ to Objects'in ifade ağaçlarını bile kullanmadığını unutmayın.
Jon Skeet

1
@gdoron: Önemli olan bu, sağlayıcının işi, Queryable'ın işi değil. Entity Framework kullanırken de önemli olmamalıdır. O does olsa LINQ nesnelere için olsun. Ama evet, kesinlikle başka bir soru sorun.
Jon Skeet

17

Evet.

Ancak bu performans farkının tam olarak ne olduğu, temeldeki ifade ağacının LINQ sağlayıcısı tarafından nasıl değerlendirildiğine bağlıdır.

Örneğin, sorgunuz LINQ-to-XML için ikinci seferde (WHERE yan tümcesi ile) daha hızlı, ancak LINQ-to-SQL için ilk seferde daha hızlı yürütülebilir.

Performans farkının tam olarak ne olduğunu öğrenmek için, büyük olasılıkla uygulamanızın profilini çıkarmak isteyeceksiniz. Bununla birlikte, bu tür şeylerde her zaman olduğu gibi, erken optimizasyon genellikle çabaya değmez - LINQ performansı dışındaki sorunların daha önemli olduğunu görebilirsiniz.


5

Senin özel örnekte bu olabilir performansına bir fark yaratmak.

İlk sorgu: OrderByÇağrınızın , 3 veya daha az olduğu öğeler dahil olmak üzere tüm kaynak dizisi boyunca yinelenmesi gerekir Code. WhereFıkra sonra da yineleme ihtiyacı tüm dizisini emretti.

İkinci sorgu: WhereÇağrı, diziyi yalnızca Code3'ten büyük olan öğelerle sınırlar . Bu durumda, OrderByçağrının yalnızca çağrının döndürdüğü indirgenmiş diziyi geçmesi gerekir Where.


3

Linq-To-Objects'te:

Sıralama oldukça yavaştır ve O(n)hafızayı kullanır . Wherediğer yandan nispeten hızlıdır ve sabit bellek kullanır. Öyle yapıyorWhere ilk daha hızlı olacak ve büyük koleksiyonlar için önemli ölçüde daha hızlı olacak.

Azaltılmış bellek baskısı da önemli olabilir çünkü büyük nesne yığınındaki ayırmalar (koleksiyonları ile birlikte) benim deneyimime göre nispeten pahalıdır.


1

Açıkçası sonuçların yine de aynı olması gerekiyordu ...

Bunun aslında doğru olmadığını unutmayın - özellikle aşağıdaki iki satır farklı sonuçlar verecektir (çoğu sağlayıcı / veri kümesi için):

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

1
Hayır, demek istediğim sonuçların optimizasyon düşünüldüğünde bile aynı olması gerektiğiydi. Bir şeyi "optimize etmenin" ve farklı bir sonuç almanın bir anlamı yok.
michael

1

Bir LINQ sorgusunu nasıl optimize edeceğinizi düşünürken dikkatli olmanız gerektiğini belirtmekte fayda var . Örneğin, aşağıdakileri yapmak için LINQ'nun bildirim temelli sürümünü kullanırsanız:

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

Her ne sebeple olursa olsun, önce ortalamayı bir değişkene kaydederek sorguyu "optimize etmeye" karar verdiyseniz, istediğiniz sonuçları alamazsınız:

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

Pek çok kişinin nesneler için bildirimsel LINQ kullanmadığını biliyorum, ancak bu düşünce için iyi bir besin.


0

Alaka düzeyine bağlıdır. Varsayalım ki Kod = 3 olan çok az öğeniz varsa, sonraki sipariş tarihe göre sipariş almak için küçük bir koleksiyon seti üzerinde çalışacaktır.

Aynı Oluşturma Tarihine sahip birçok öğeniz varsa, sonraki sipariş, tarihe göre sipariş almak için daha büyük bir koleksiyon setinde çalışacaktır.

Yani, her iki durumda da performansta bir fark olacaktır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.