Neden Where ve Select'in performansı sadece Select'ten daha iyi?


145

Bunun gibi bir dersim var:

public class MyClass
{
    public int Value { get; set; }
    public bool IsValid { get; set; }
}

Aslında çok daha büyüktür, ancak bu sorunu yeniden yaratır (tuhaflık).

ValueÖrneğin geçerli olduğu toplamını almak istiyorum . Şimdiye kadar buna iki çözüm buldum.

İlki şu:

int result = myCollection.Where(mc => mc.IsValid).Select(mc => mc.Value).Sum();

İkincisi ise şudur:

int result = myCollection.Select(mc => mc.IsValid ? mc.Value : 0).Sum();

En verimli yöntemi elde etmek istiyorum. İlk başta ikincisinin daha verimli olacağını düşündüm. Sonra teorik kısmım "Peki, biri O (n + m + m), diğeri O (n + n). Birincisi daha fazla sakatla daha iyi performans göstermeli, ikincisi daha iyi performans göstermeli daha az ". Eşit performans göstereceklerini düşündüm. DÜZENLEME: Ve sonra @Martin, Where ve Select'in birleştirildiğine işaret etti, bu yüzden aslında O (m + n) olması gerektiğini söyledi. Ancak, aşağıya bakarsanız, bunun alakası yok gibi görünüyor.


Ben de teste tabi tuttum.

(100'den fazla satır, bu yüzden bir
Özet olarak göndermenin daha iyi olacağını düşündüm.) Sonuçlar ... ilginçti.

% 0 bağ toleransı ile:

Ölçekler yanayız Selectve Where~ 30 noktaları hakkında tarafından.

How much do you want to be the disambiguation percentage?
0
Starting benchmarking.
Ties: 0
Where + Select: 65
Select: 36

% 2 bağ toleransı ile:

Aynı şey, bazıları için% 2 içindeydi. Bunun minimum hata payı olduğunu söyleyebilirim. Selectve Whereşimdi sadece ~ 20 puan önde.

How much do you want to be the disambiguation percentage?
2
Starting benchmarking.
Ties: 6
Where + Select: 58
Select: 37

% 5 bağ toleransı ile:

Bu benim maksimum hata payım olduğunu söyleyebilirim. Bunu biraz daha iyi yapar Select, ama çok değil.

How much do you want to be the disambiguation percentage?
5
Starting benchmarking.
Ties: 17
Where + Select: 53
Select: 31

% 10 bağ toleransı ile:

Bu, hata payımın dışında, ama yine de sonuçla ilgileniyorum. O verir çünkü Selectve Whereyirmi nokta kurşun şimdi bir süre yaşadı.

How much do you want to be the disambiguation percentage?
10
Starting benchmarking.
Ties: 36
Where + Select: 44
Select: 21

% 25 bağ toleransı ile:

Bu şekilde, bir yolu hata benim marjı dışında, ama çünkü ben hala sonuç ilgilenen kulüpler Selectve Where hala (neredeyse) onların 20 puan önde tutun. Görünüşe göre onu birkaç tanede geride bırakıyor ve ona liderlik eden de bu.

How much do you want to be the disambiguation percentage?
25
Starting benchmarking.
Ties: 85
Where + Select: 16
Select: 0


Şimdi, 20 farkla öndeler ikisi de almak için bağlı konum ortada, geldiğini tahmin ediyorum etrafında aynı performansı. Bunu deneyebilir ve kaydedebilirdim, ama almak için çok fazla bilgi olurdu. Bir grafik daha iyi olurdu, sanırım.

Ben de öyle yaptım.

Seç ve Nerede vs seçin.

Bu gösteriyor ki Selecthat istikrarlı tutar (beklenen) ve bu Select + Wherehat yukarı tırmanır (beklenen). Onunla uymayan neden Ancak, ne Kafamı kurcalayan olan Selectekstra listeleyicisi için oluşturulan gerekiyordu olarak, aslında 50'den önceki bekliyordum: 50 veya daha erken Selectve Where. Demek istediğim, bu 20 puanlık farkı gösteriyor ama nedenini açıklamıyor. Sanırım sorumun ana noktası bu.

Neden böyle davranıyor? Güvenmeli miyim? Değilse, diğerini mi yoksa bunu mu kullanmalıyım?


@KingKong'un yorumlarda bahsettiği gibi, Sumlambda alan aşırı yüklemeyi de kullanabilirsiniz . Yani iki seçeneğim şimdi şu şekilde değiştirildi:

İlk:

int result = myCollection.Where(mc => mc.IsValid).Sum(mc => mc.Value);

İkinci:

int result = myCollection.Sum(mc => mc.IsValid ? mc.Value : 0);

Biraz kısaltacağım ama:

How much do you want to be the disambiguation percentage?
0
Starting benchmarking.
Ties: 0
Where: 60
Sum: 41
How much do you want to be the disambiguation percentage?
2
Starting benchmarking.
Ties: 8
Where: 55
Sum: 38
How much do you want to be the disambiguation percentage?
5
Starting benchmarking.
Ties: 21
Where: 49
Sum: 31
How much do you want to be the disambiguation percentage?
10
Starting benchmarking.
Ties: 39
Where: 41
Sum: 21
How much do you want to be the disambiguation percentage?
25
Starting benchmarking.
Ties: 85
Where: 16
Sum: 0

Yirmi puanlık liderlik hala oradadır, yani yorumlarda @ Marcin tarafından belirtilen Whereve Selectkombinasyonuyla bir ilgisi yoktur .

Metin duvarımı okuduğunuz için teşekkürler! Ayrıca, ilgileniyorsanız, Excel'in aldığı CSV'yi günlüğe kaydeden değiştirilmiş sürümü burada bulabilirsiniz .


1
Toplamın ve erişimin ne kadar pahalı olduğuna bağlı olduğunu söyleyebilirim mc.Value.
Medinoc

14
@ BuNotALie. Where+ Select, girdi toplama üzerinde iki ayrı yinelemeye neden olmaz. LINQ to Objects, onu tek bir yinelemede optimize eder. Blog
yazım hakkında daha fazlasını

4
İlginç. Bir dizi üzerindeki for döngüsünün en iyi LINQ çözümünden 10 kat daha hızlı olacağını belirtmeme izin verin. Bu nedenle, mükemmellik için avlanmaya gidiyorsanız, ilk olarak LINQ kullanmayın.
usr

2
Bazen insanlar gerçek bir araştırmadan sonra sorarlar, bu bir örnek sorudur: Ben C # kullanıcısı değilim, Sıcak soru listesinden geldi.
Grijesh Chauhan

2
@WiSaGaN Bu iyi bir nokta. Bununla birlikte, eğer bu dallanma ve koşullu hareketten kaynaklanıyorsa, en dramatik farkı% 50 /% 50 olarak görmeyi bekleriz. Burada, dallanmanın en tahmin edilebilir olduğu uçlarda en dramatik farklılıkları görüyoruz. Nerede bir dalsa ve üçlü bir koşullu hareketse, tüm unsurlar geçerli olduğunda Nerede zamanlarının geri gelmesini bekleriz, ancak asla geri gelmez.
John Tseng

Yanıtlar:


131

Selecttüm set üzerinde bir kez yineler ve her öğe için bir koşullu dallanma (geçerliliği denetleme) ve bir +işlem gerçekleştirir.

Where+Selectgeçersiz öğeleri atlayan ( yieldonları değil ), +yalnızca geçerli öğeler üzerinde gerçekleştiren bir yineleyici oluşturur .

Yani, a'nın maliyeti Select:

t(s) = n * ( cost(check valid) + cost(+) )

Ve için Where+Select:

t(ws) = n * ( cost(check valid) + p(valid) * (cost(yield) + cost(+)) )

Nerede:

  • p(valid) listedeki bir öğenin geçerli olma olasılığıdır.
  • cost(check valid) geçerliliğini kontrol eden şubenin maliyetidir
  • cost(yield)sürümün kullandığı wherebasit yineleyiciden daha karmaşık olan yineleyicinin yeni durumunu oluşturmanın maliyetidir Select.

Gördüğünüz gibi, belirli bir sürüm niçin Selectsürüm sabittir, Where+Selectsürüm ise p(valid)değişken olarak doğrusal bir denklemdir . Maliyetlerin gerçek değerleri iki çizginin kesişme noktasını belirler ve cost(yield)bundan farklı olabileceğinden cost(+), mutlaka p(valid)= 0.5'de kesişmezler .


34
+1 (şimdiye kadar) soruyu gerçekten ele alan , cevabı tahmin etmeyen ve sadece "ben de" oluşturmayan tek cevap olduğu için +1 ! İstatistik.
Binary Worrier

4
Teknik olarak LINQ yöntemleri, "setler" yerine tüm koleksiyonda bir kez çalıştırılan ifade ağaçları oluşturur.
Spoike

Nedir cost(append)? Gerçekten iyi cevap olsa da, sadece istatistikten ziyade farklı bir açıdan bakıyor.
NotALie.

5
Whereherhangi bir şey yaratmaz, sadece sourceyüklemi dolduruyorsa sıradan bir seferde bir öğe döndür.
MarcinJuraszek

13
@Spoike - İfade ağaçları burada alakalı değildir, çünkü bu, nesnelere -linq, başka bir şeye değil (örneğin Varlık). İşte arasındaki fark bu IEnumerable.Select(IEnumerable, Func)ve IQueryable.Select(IQueryable, Expression<Func>). LINQ, koleksiyonu tekrarlayana kadar "hiçbir şey" yapmadığında haklısın, muhtemelen bunu kastettin.
Kobi

33

İşte zamanlama farklılıklarına neden olan şeyin derinlemesine bir açıklaması.


Sum()Fonksiyon IEnumerable<int>böyle görünüyor:

public static int Sum(this IEnumerable<int> source)
{
    int sum = 0;
    foreach(int item in source)
    {
        sum += item;
    }
    return sum;
}

C # 'da, foreach.Net'in bir yineleyici sürümü için sözdizimsel şekerdir (karıştırılmamalıdır ) . Yani yukarıdaki kod aslında buna çevrilmiştir:IEnumerator<T> IEnumerable<T>

public static int Sum(this IEnumerable<int> source)
{
    int sum = 0;

    IEnumerator<int> iterator = source.GetEnumerator();
    while(iterator.MoveNext())
    {
        int item = iterator.Current;
        sum += item;
    }
    return sum;
}

Unutmayın, karşılaştırdığınız iki kod satırı aşağıdaki gibidir

int result1 = myCollection.Where(mc => mc.IsValid).Sum(mc => mc.Value);
int result2 = myCollection.Sum(mc => mc.IsValid ? mc.Value : 0);

İşte önemli nokta:

LINQ, ertelenmiş yürütmeyi kullanır . Bu nedenle, koleksiyon üzerinde iki kez yineleniyor gibi görünse de result1, aslında yalnızca bir kez yineler. Durum Where()aslında Sum()çağrı sırasında , içinde uygulanır MoveNext() (Bu sihir sayesinde mümkündür yield return) .

Bu result1, whiledöngünün içindeki kod için ,

{
    int item = iterator.Current;
    sum += item;
}

ile her öğe için yalnızca bir kez yürütülür mc.IsValid == true. Karşılaştırıldığında, koleksiyondaki her öğe result2için bu kodu çalıştıracaktır . Genelde daha hızlı olmasının nedeni budur.result1

(Yine de, Where()içindeki koşulu çağırmanın MoveNext()hala bazı küçük ek yükleri olduğunu unutmayın , bu nedenle öğelerin çoğu / tümü varsa mc.IsValid == true, result2aslında daha hızlı olacaktır!)


Umarım şimdi neden result2genellikle daha yavaş olduğu açıktır . Şimdi, yorumlarda bu LINQ performans karşılaştırmalarının önemli olmadığını neden belirttiğimi açıklamak istiyorum .

LINQ ifadesi oluşturmak ucuzdur. Temsilci işlevlerini aramak ucuzdur. Bir yineleyici için tahsis ve döngü yapmak ucuzdur. Ama bunları yapmamak daha da ucuz . Bu nedenle, bir LINQ ifadesinin programınızdaki darboğaz olduğunu fark ederseniz, benim deneyimime göre onu LINQ olmadan yeniden yazmak, onu her zaman çeşitli LINQ yöntemlerinden daha hızlı hale getirecektir.

Dolayısıyla, LINQ iş akışınız şu şekilde görünmelidir:

  1. LINQ'i her yerde kullanın.
  2. Profil.
  3. Profil oluşturucu, LINQ'nun bir darboğazın nedeni olduğunu söylüyorsa, bu kod parçasını LINQ olmadan yeniden yazın.

Neyse ki, LINQ darboğazları nadirdir. Heck, darboğazlar nadirdir. Son birkaç yıl içinde yüzlerce LINQ ifadesi yazdım ve <% 1'in yerini aldım. Ve çoğu bu ödenecek LINQ2EF yerine LINQ hatası olmaktan çok, 'in fakir SQL optimizasyonu.

Bu nedenle, her zaman olduğu gibi önce açık ve mantıklı bir kod yazın ve sonrasına kadar bekleyin. mikro-optimizasyonlar hakkında endişe etmek profilli ettik.


3
Küçük ek: En üstteki cevap düzeltildi.
NotALie.

16

Komik şey. Nasıl Sum(this IEnumerable<TSource> source, Func<TSource, int> selector)tanımlandığını biliyor musun ? Yöntemi kullanır Select!

public static int Sum<TSource>(this IEnumerable<TSource> source, Func<TSource, int> selector)
{
    return source.Select(selector).Sum();
}

Yani aslında hepsi neredeyse aynı şekilde çalışmalı. Kendi başıma hızlı araştırma yaptım ve işte sonuçlar:

Where -- mod: 1 result: 0, time: 371 ms
WhereSelect -- mod: 1  result: 0, time: 356 ms
Select -- mod: 1  result 0, time: 366 ms
Sum -- mod: 1  result: 0, time: 363 ms
-------------
Where -- mod: 2 result: 4999999, time: 469 ms
WhereSelect -- mod: 2  result: 4999999, time: 429 ms
Select -- mod: 2  result 4999999, time: 362 ms
Sum -- mod: 2  result: 4999999, time: 358 ms
-------------
Where -- mod: 3 result: 9999999, time: 441 ms
WhereSelect -- mod: 3  result: 9999999, time: 452 ms
Select -- mod: 3  result 9999999, time: 371 ms
Sum -- mod: 3  result: 9999999, time: 380 ms
-------------
Where -- mod: 4 result: 7500000, time: 571 ms
WhereSelect -- mod: 4  result: 7500000, time: 501 ms
Select -- mod: 4  result 7500000, time: 406 ms
Sum -- mod: 4  result: 7500000, time: 397 ms
-------------
Where -- mod: 5 result: 7999999, time: 490 ms
WhereSelect -- mod: 5  result: 7999999, time: 477 ms
Select -- mod: 5  result 7999999, time: 397 ms
Sum -- mod: 5  result: 7999999, time: 394 ms
-------------
Where -- mod: 6 result: 9999999, time: 488 ms
WhereSelect -- mod: 6  result: 9999999, time: 480 ms
Select -- mod: 6  result 9999999, time: 391 ms
Sum -- mod: 6  result: 9999999, time: 387 ms
-------------
Where -- mod: 7 result: 8571428, time: 489 ms
WhereSelect -- mod: 7  result: 8571428, time: 486 ms
Select -- mod: 7  result 8571428, time: 384 ms
Sum -- mod: 7  result: 8571428, time: 381 ms
-------------
Where -- mod: 8 result: 8749999, time: 494 ms
WhereSelect -- mod: 8  result: 8749999, time: 488 ms
Select -- mod: 8  result 8749999, time: 386 ms
Sum -- mod: 8  result: 8749999, time: 373 ms
-------------
Where -- mod: 9 result: 9999999, time: 497 ms
WhereSelect -- mod: 9  result: 9999999, time: 494 ms
Select -- mod: 9  result 9999999, time: 386 ms
Sum -- mod: 9  result: 9999999, time: 371 ms

Aşağıdaki uygulamalar için:

result = source.Where(x => x.IsValid).Sum(x => x.Value);
result = source.Select(x => x.IsValid ? x.Value : 0).Sum();
result = source.Sum(x => x.IsValid ? x.Value : 0);
result = source.Where(x => x.IsValid).Select(x => x.Value).Sum();

modşu anlama gelir: modöğelerden her 1'i geçersizdir: mod == 1her öğe için geçersiz, mod == 2tek sayılar için geçersiz vb. Koleksiyon 10000000öğeler içeriyor .

görüntü açıklamasını buraya girin

Ve 100000000öğelerle koleksiyon için sonuçlar :

görüntü açıklamasını buraya girin

Gördüğünüz gibi, Selectve Sumsonuç tümündeki oldukça tutarlıdır moddeğerler. Ancak whereve where+ selectdeğildir.


1
Sonuçlarınızda tüm yöntemlerin aynı yerden başlayıp birbirinden uzaklaşması çok ilginçtir, oysa It'sNotALie'nin sonuçları ortada kesişir.
John Tseng

6

Tahminimce Where ile 0'ları filtreler ve bunlar Sum için bir konu değildir (yani eklemeyi yürütmüyorsunuz). Bu elbette bir tahmin çünkü ek lambda ifadesini çalıştırmanın ve birden çok yöntemi çağırmanın nasıl basit bir 0 eklemesinden daha iyi performans gösterdiğini açıklayamıyorum.

Bir arkadaşım, toplamdaki 0'ın taşma kontrolleri nedeniyle ciddi performans cezasına neden olabileceğini söyledi. Bunun kontrolsüz bir bağlamda nasıl performans göstereceğini görmek ilginç olurdu.


İle yapılan bazı testler unchecked, onu Select.
NotALie.

Birisi, işaretlenmemesinin yığından çağrılan yöntemleri veya yalnızca üst düzey işlemleri etkilediğini söyleyebilir mi?
Stilgar

1
@Stilgar Sadece üst seviye için geçerlidir.
Branko Dimitrijevic

Yani belki de kontrol edilmemiş Toplamı uygulamalı ve bu şekilde denemeliyiz.
Stilgar

5

Aşağıdaki örneği çalıştırdığımda, + Select'in Select'ten daha iyi performans gösterebileceği tek zamanın aslında listedeki potansiyel öğelerin iyi bir miktarını (gayri resmi testlerimde yaklaşık yarısı) attığı zaman olduğu bana açık hale geliyor. Aşağıdaki küçük örnekte, 10mil'de yaklaşık 4mil öğeyi atladığında her iki örnekten de aşağı yukarı aynı sayıları alıyorum. Sürümde koştum ve where + select vs select uygulamasını aynı sonuçlarla yeniden sıraladım.

static void Main(string[] args)
        {
            int total = 10000000;
            Random r = new Random();
            var list = Enumerable.Range(0, total).Select(i => r.Next(0, 5)).ToList();
            for (int i = 0; i < 4000000; i++)
                list[i] = 10;

            var sw = new Stopwatch();
            sw.Start();

            int sum = 0;

            sum = list.Where(i => i < 10).Select(i => i).Sum();            

            sw.Stop();
            Console.WriteLine(sw.ElapsedMilliseconds);

            sw.Reset();
            sw.Start();
            sum = list.Select(i => i).Sum();            

            sw.Stop();

            Console.WriteLine(sw.ElapsedMilliseconds);
        }

Bu, içindeki on altıları atmadığın için değil Selectmi?
NotALie.

3
Hata ayıklamada çalıştırmak işe yaramaz.
MarcinJuraszek

1
@MarcinJuraszek Açıkçası. Gerçekten piyasaya sürüldüğümü söylemek istemiştim :)
DavidN

@ It'sNotALie Bu nokta. Bana öyle geliyor ki, + Select'in, Select'ten daha iyi performans göstermesinin tek yolu, nerede toplanan büyük miktarda öğenin filtrelendiği zamandır.
DavidN

2
Temelde sorumun belirttiği bu. Bu örnekte olduğu gibi yaklaşık% 60 oranında bağlarlar. Soru, burada cevaplanmayan neden.
NotALie.

4

Hıza ihtiyacınız varsa, sadece basit bir döngü yapmak muhtemelen en iyi seçeneğinizdir. Ve yapmak fordaha iyi olma eğilimindedirforeach (koleksiyonunuzun rastgele erişim olduğunu varsayarsak) .

Öğelerin% 10'unun geçersiz olduğu zamanlamalar şu şekildedir:

Where + Select + Sum:   257
Select + Sum:           253
foreach:                111
for:                    61

Ve% 90 geçersiz öğelerle:

Where + Select + Sum:   177
Select + Sum:           247
foreach:                105
for:                    58

Ve işte kıyaslama kodum ...

public class MyClass {
    public int Value { get; set; }
    public bool IsValid { get; set; }
}

class Program {

    static void Main(string[] args) {

        const int count = 10000000;
        const int percentageInvalid = 90;

        var rnd = new Random();
        var myCollection = new List<MyClass>(count);
        for (int i = 0; i < count; ++i) {
            myCollection.Add(
                new MyClass {
                    Value = rnd.Next(0, 50),
                    IsValid = rnd.Next(0, 100) > percentageInvalid
                }
            );
        }

        var sw = new Stopwatch();
        sw.Restart();
        int result1 = myCollection.Where(mc => mc.IsValid).Select(mc => mc.Value).Sum();
        sw.Stop();
        Console.WriteLine("Where + Select + Sum:\t{0}", sw.ElapsedMilliseconds);

        sw.Restart();
        int result2 = myCollection.Select(mc => mc.IsValid ? mc.Value : 0).Sum();
        sw.Stop();
        Console.WriteLine("Select + Sum:\t\t{0}", sw.ElapsedMilliseconds);
        Debug.Assert(result1 == result2);

        sw.Restart();
        int result3 = 0;
        foreach (var mc in myCollection) {
            if (mc.IsValid)
                result3 += mc.Value;
        }
        sw.Stop();
        Console.WriteLine("foreach:\t\t{0}", sw.ElapsedMilliseconds);
        Debug.Assert(result1 == result3);

        sw.Restart();
        int result4 = 0;
        for (int i = 0; i < myCollection.Count; ++i) {
            var mc = myCollection[i];
            if (mc.IsValid)
                result4 += mc.Value;
        }
        sw.Stop();
        Console.WriteLine("for:\t\t\t{0}", sw.ElapsedMilliseconds);
        Debug.Assert(result1 == result4);

    }

}

BTW, Stilgar'ın tahminiyle aynı fikirdeyim : İki durumunuzun göreceli hızları geçersiz öğelerin yüzdesine bağlı olarak değişir, çünkü Sumyapılması gereken iş miktarı "Nerede" durumunda değişir.


1

Açıklama yoluyla açıklamaya çalışmak yerine, daha matematiksel bir yaklaşım benimseyeceğim.

LINQ'nun dahili olarak ne yaptığını yaklaşık olarak göstermesi gereken aşağıdaki kod göz önüne alındığında, göreceli maliyetler aşağıdaki gibidir:
Yalnızca seçin:Nd + Na
Where + Select:Nd + Md + Ma

Kesişecekleri noktayı bulmak için biraz cebir yapmamız gerekiyor:
Nd + Md + Ma = Nd + Na => M(d + a) = Na => (M/N) = a/(d+a)

Bunun anlamı, bükülme noktasının% 50 olması için, bir delege çağrısının maliyetinin kabaca bir ekleme maliyetiyle aynı olması gerektiğidir. Gerçek bükülme noktasının yaklaşık% 60 olduğunu bildiğimizden, geriye doğru çalışabilir ve @ It'sNotALie için bir delege çağrısının maliyetinin aslında bir eklemenin maliyetinin yaklaşık 2 / 3'ü olduğunu belirleyebiliriz ki bu şaşırtıcıdır, ancak bu numaraları diyor.

static void Main(string[] args)
{
    var set = Enumerable.Range(1, 10000000)
                        .Select(i => new MyClass {Value = i, IsValid = i%2 == 0})
                        .ToList();

    Func<MyClass, int> select = i => i.IsValid ? i.Value : 0;
    Console.WriteLine(
        Sum(                        // Cost: N additions
            Select(set, select)));  // Cost: N delegate
    // Total cost: N * (delegate + addition) = Nd + Na

    Func<MyClass, bool> where = i => i.IsValid;
    Func<MyClass, int> wSelect = i => i.Value;
    Console.WriteLine(
        Sum(                        // Cost: M additions
            Select(                 // Cost: M delegate
                Where(set, where),  // Cost: N delegate
                wSelect)));
    // Total cost: N * delegate + M * (delegate + addition) = Nd + Md + Ma
}

// Cost: N delegate calls
static IEnumerable<T> Where<T>(IEnumerable<T> set, Func<T, bool> predicate)
{
    foreach (var mc in set)
    {
        if (predicate(mc))
        {
            yield return mc;
        }
    }
}

// Cost: N delegate calls
static IEnumerable<int> Select<T>(IEnumerable<T> set, Func<T, int> selector)
{
    foreach (var mc in set)
    {
        yield return selector(mc);
    }
}

// Cost: N additions
static int Sum(IEnumerable<int> set)
{
    unchecked
    {
        var sum = 0;
        foreach (var i in set)
        {
            sum += i;
        }

        return sum;
    }
}

0

Marcin Juraszek'in sonucunun It'sNotALie'nin sonucundan farklı olması ilginç bence. Özellikle, MarcinJuraszek'in sonuçları dört uygulamanın hepsinin aynı yerde olmasıyla başlarken, It'sNotALie'nin sonuçları ortada kesişiyor. Bunun nasıl çalıştığını kaynaktan açıklayacağım.

nToplam unsurlar ve mgeçerli unsurlar olduğunu varsayalım .

SumFonksiyon oldukça basittir. Yalnızca numaralandırıcıda döngü yapar: http://typedescriptor.net/browse/members/367300-System.Linq.Enumerable.Sum(IEnumerable%601)

Basit olması için, koleksiyonun bir liste olduğunu varsayalım. Hem Seç ve WhereSelect bir yaratacaktır WhereSelectListIterator. Bu, üretilen gerçek yineleyicilerin aynı olduğu anlamına gelir. Her iki durumda da, Sumbir yineleyici üzerinde döngü yapan bir WhereSelectListIterator. Yineleyicinin en ilginç kısmı MoveNext yöntemidir.

Yineleyiciler aynı olduğu için döngüler aynıdır. Tek fark, döngülerin gövdesindedir.

Bu lambdaların bedeni çok benzer maliyetlidir. Where cümlesi bir alan değeri döndürür ve üçlü yüklem de bir alan değeri döndürür. Select yan tümcesi bir alan değeri döndürür ve üçlü operatörün iki dalı bir alan değeri veya bir sabit döndürür. Birleşik select yan tümcesi, dalı üçlü operatör olarak içerir, ancak WhereSelect şubeyi içinde kullanır MoveNext.

Ancak tüm bu işlemler oldukça ucuzdur. Şimdiye kadarki en pahalı operasyon, yanlış bir tahminin bize mal olacağı şubedir.

Buradaki diğer bir pahalı işlem Invoke. Branko Dimitrijevic'in gösterdiği gibi, bir işlevi çağırmak, bir değer eklemekten biraz daha uzun sürer.

Ayrıca tartım, kontrol edilen birikimdir. Sum . İşlemcinin aritmetik bir taşma bayrağı yoksa, bunu kontrol etmek de maliyetli olabilir.

Dolayısıyla, ilginç maliyetler:

  1. ( n+ m) * Çağır + m*checked+=
  2. n* Çağır + n*checked+=

Bu nedenle, Invoke'un maliyeti kontrol edilen birikim maliyetinden çok daha yüksekse, durum 2 her zaman daha iyidir. Yaklaşık çiftlerse, öğelerin yaklaşık yarısı geçerli olduğunda bir denge göreceğiz.

Görünüşe göre MarcinJuraszek'in sisteminde, işaretli + = ihmal edilebilir bir maliyete sahip, ancak It'sNotALie ve Branko Dimitrijevic'in sistemlerinde + = önemli maliyetler var. Başabaş noktası çok daha yüksek olduğu için, It'sNotALie'nin sistemindeki en pahalı gibi görünüyor. Görünüşe göre hiç kimse, biriktirmenin Invoke'tan çok daha pahalı olduğu bir sistemden sonuçlar göndermiş gibi görünmüyor.


@ BuNotALie. Kimsenin yanlış bir sonucu olduğunu sanmıyorum. Sadece bazı şeyleri açıklayamadım. Invoke'un maliyetinin + = maliyetinden çok daha yüksek olduğunu varsaymıştım, ancak donanım optimizasyonlarına bağlı olarak çok daha yakın olabilecekleri düşünülebilir.
John Tseng
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.