SORU
21 Kasım 2011, PAZARTESİ


foreach break vs etmeniz FirstOrDefault performans farkı

Tarih aralığı verileri belirli gün alınıyor tarih gerçekleştiren iki sınıf var.

public class IterationLookup<TItem>
{
    private IList<Item> items = null;

    public IterationLookup(IEnumerable<TItem> items, Func<TItem, TKey> keySelector)
    {
        this.items = items.OrderByDescending(keySelector).ToList();
    }

    public TItem GetItem(DateTime day)
    {
        foreach(TItem i in this.items)
        {
           if (i.IsWithinRange(day))
           {
               return i;
           }
        }
        return null;
    }
}


public class LinqLookup<TItem>
{
    private IList<Item> items = null;

    public IterationLookup(IEnumerable<TItem> items, Func<TItem, TKey> keySelector)
    {
        this.items = items.OrderByDescending(keySelector).ToList();
    }

    public TItem GetItem(DateTime day)
    {
        return this.items.FirstOrDefault(i => i.IsWithinRange(day));
    }
}

Sonra Seri sürümü ile ilgili olduğunu gösteren testler hız yapıyorum5 kez daha yavaş. Bu öğeler yerel olarak ToList kullanarak numaralandırma olmadan depolamak için mantıklı olur. Bu çok yavaş, ayrıca OrderByDescending performans göstereceğini seri FirstOrDefault, Her dediği ile yapacaktır. Ama gerçekten neler olup bittiğini bilmiyorum öyle böyle değil.Seri yineleme çok benzer yapmalıdır.

Bu benim zamanlamaları ölçen kod alıntıdır

IList<RangeItem> ranges = GenerateRanges(); // returns List<T>

var iterLookup = new IterationLookup<RangeItems>(ranges, r => r.Id);
var linqLookup = new LinqLookup<RangeItems>(ranges, r => r.Id);

Stopwatch timer = new Stopwatch();

timer.Start();
for(int i = 0; i < 1000000; i  )
{
    iterLookup.GetItem(GetRandomDay());
}
timer.Stop();
// display elapsed time

timer.Restart();
for(int i = 0; i < 1000000; i  )
{
    linqLookup.GetItem(GetRandomDay());
}
timer.Stop();
// display elapsed time

Neden daha iyisini yapmak gerektiğini biliyor musunuz? Bu ara sınıfları kullanmadan çok benzer bir kod yazıyorum çünkü Seri foreach tekrarlamalar çok benzer... gerçekleştirir

// continue from previous code block

// items used by both order as they do in classes as well
IList<RangeItem> items = ranges.OrderByDescending(r => r.Id).ToList();

timer.Restart();
for(int i = 0; i < 1000000; i  )
{
    DateTime day = GetRandomDay();
    foreach(RangeItem r in items)
    {
        if (r.IsWithinRange(day))
        {
            // RangeItem result = r;
            break;
        }
    }
}    
timer.Stop();
// display elapsed time

timer.Restart();
for(int i = 0; i < 1000000; i  )
{
   DateTime day = GetRandomDay();
   items.FirstOrDefault(i => i.IsWithinRange(day));
}
timer.Stop();
// display elapsed time

Bu bence son derece benzer bir kod. FirstOrDefault bildiğim kadarını da geçerli bir öğe alır sadece sürece kadar veya sonuna kadar yineleme. Ve bu bir şekilde break *ile 21* ile aynıdır.

Ama bile yineleme sınıf kötü bütün yükü doğrudan erişim göre bir sınıf içinde bir yöntem çağrısı olduğu için de bir gizem olan foreach yineleme basit döngüm daha gerçekleştirir.

Soru

Yanlış çok yavaş gerçekleştiren ETMENİZ benim sınıfta ne yapıyorum?
foreach doğrudan döngü iki kat daha yavaş yapması Yineleme sınıfımda neyi yanlış yapıyorum?

Hangi kez ölçülüyor?

Ben bu adımları yapın:

  1. Aralıkları aşağıda sonuçlarında görüldüğü gibi) oluşturmak
  2. İterationLookup, LinqLookup için nesne örneklerini (ve tartışma bölümü burada değil optimize tarih aralığı sınıf benim BitCountLookup) oluşturun
  3. Zamanlayıcı başlar ve en fazla tarih içinde rastgele günlerde 1 milyon arama yürütmek daha önce örneği İterationLookup sınıf kullanarak dizi sonuçlarında görüldüğü gibi).
  4. Zamanlayıcı başlar ve en fazla tarih içinde rastgele günlerde 1 milyon arama yürütmek daha önce örneği LinqLookup sınıf kullanarak dizi sonuçlarında görüldüğü gibi).
  5. Sayacı başlatmak ve 1 milyon aramaları (6 kez) manuel dosyalarda grup break döngüleri ve Seri çağrılarını kullanarak yürütmek.

Gördüğünüz gibinesne örnekleme ölçülür.

Ek I: Sonuçlar üzerinden milyon arama

Aralıkları bu sonuçlar görüntülenen ETMENİZ sürümü başarılı maç büyük ihtimalle) döngü sonu gelmez bu durumda her iki yaklaşım daha benzer yapmak gerekir örtüşme yok.

Generated Ranges:

ID Range        000000000111111111122222222223300000000011111111112222222222
                123456789012345678901234567890112345678901234567890123456789
09 22.01.-30.01.                     |-------|
08 14.01.-16.01.             |-|
07 16.02.-19.02.                                              |--|
06 15.01.-17.01.              |-|
05 19.02.-23.02.                                                 |---|
04 01.01.-07.01.|-----|
03 02.01.-10.01. |-------|
02 11.01.-13.01.          |-|
01 16.01.-20.01.               |---|
00 29.01.-06.02.                            |-------|

Lookup classes...

- Iteration: 1028ms
- Linq: 4517ms   !!! THIS IS THE PROBLEM !!!
- BitCounter: 401ms

Manual loops...

- Iter: 786ms
- Linq: 981ms
- Iter: 787ms
- Linq: 996ms
- Iter: 787ms
- Linq: 977ms
- Iter: 783ms
- Linq: 979ms

Ek II: GitHub:Özü kendinizi test etmek için kod

Tam kodu gir ve neler olduğunu görebilmeniz için bir Özet hazırladım. Bir oluşturunKonsoluygulama ve kopyalayınProgram.csbir bu özü bir parçası olan diğer dosyaları ekleyin.

here al.

Ek III: Final düşünceler ve ölçüm testleri

En sorunlu şey çok yavaş olduğunu elbette ETMENİZ implementatino. Bu temsilci derleyici ile optimizasyonu. olduğu ortaya çıktı Aslında beni denemeye yapan LukeH provided the best and most usable solution bu yaklaşımlar. GetItem yöntem (veya Özü de adı geçtiği gibi GetPointData) çeşitli farklı yaklaşımlar denedim:

  1. geliştiriciler çoğu (ve Esas olarak uygulanır ve sonuç bu, bunu yapmanın en iyi yolu değil ortaya sonra güncelleştirilmiş değildi) ki her zamanki gibi:

    return this.items.FirstOrDefault(item => item.IsWithinRange(day));
    
  2. yerel bir yüklem değişkeni tanımlayarak:

    Func<TItem, bool> predicate = item => item.IsWithinRange(day);
    return this.items.FirstOrDefault(predicate);
    
  3. yerel yüklem builder:

    Func<DateTime, Func<TItem, bool>> builder = d => item => item.IsWithinRange(d);
    return this.items.FirstOrDefault(builder(day));
    
  4. yerel yüklem builder ve yerel yüklem değişken:

    Func<DateTime, Func<TItem, bool>> builder = d => item => item.IsWithinRange(d);
    Func<TItem, bool> predicate = builder(day);
    return this.items.FirstOrDefault(predicate);
    
  5. sınıf düzeyi (statik veya örnek) builder yüklem:

    return this.items.FirstOrDefault(classLevelBuilder(day));
    
  6. dışarıdan ve Yöntem parametre olarak yüklem sağlanan tanımlanmış

    public TItem GetItem(Func<TItem, bool> predicate)
    {
        return this.items.FirstOrDefault(predicate);
    }
    

    bu yöntemi uygularken ben de iki yaklaşım aldı:

    1. yüklem doğrudan temin yöntemi for döngü içinde çağrı:

      for (int i = 0; i < 1000000; i  )
      {
          linqLookup.GetItem(item => item.IsWithinRange(GetRandomDay()));
      }
      
    2. yüklem builder for döngü dışında tanımlanmış:

      Func<DateTime, Func<Ranger, bool>> builder = d => r => r.IsWithinRange(d);
      for (int i = 0; i < 1000000; i  )
      {
          linqLookup.GetItem(builder(GetRandomDay()));
      }
      

En iyi ne yapar sonuçları

Yineleme sınıf kullanarak karşılaştırma için, bu yaklaşık alır.770msrasgele oluşturulan aralıklara 1 milyon arama yürütmek için.

  1. Yerel yüklem builder neredeyse hızlı yineleme her zamanki gibi yapması en iyi derleyici optimize edilmiş olarak çıkıyor #3.800ms.
  2. Builder yüklem #6.2 for döngü dışında tanımlanmış:885ms
  3. #6.1 yüklem for döngü içinde tanımlanan:1525ms
  4. Tüm Diğerleri arasında bir yere götürdü4200ms - 4360msve böylece kullanılamaz olarak kabul edilir.

Çağrı yöntemi sık dışarıdan bir yüklem kullanmak her zaman bir inşaatçı tanımlamak ve yürütmek. Bu en iyi sonuçları verecektir.

Bu konuda bana en büyük sürpriz temsilciler (veya ifade) bu çok zaman alıcı olabilir.

CEVAP
21 Kasım 2011, PAZARTESİ


Bir döngü içinde delege nesil (özellikle yöntem çağrıları üzerine çok açık olmayan bir döngü) zaman ekleyebilirsiniz çünkü bazen ETMENİZ daha yavaş görünür. Bunun yerine, sınıfın dışında Bulucu daha genel anahtar seçici inşaat gibi) yapmak için hareketli düşünebilirsiniz:

public class LinqLookup<TItem, TKey>
{
    private IList<Item> items = null;

    public IterationLookup(IEnumerable<TItem> items, Func<TItem, TKey> keySelector)
    {
        this.items = items.OrderByDescending(keySelector).ToList();
    }

    public TItem GetItem(Func<TItem, TKey> selector)
    {
        return this.items.FirstOrDefault(selector);
    }
}

Yinelemeli kod lambda kullanmayın, çünkü bu döngü her geçişte temsilci oluşturmak için olduğu için bir fark olabiliyor. Genellikle, bu kez önemsiz her gün kodlama ve zaman çağırmak için temsilci hayır daha pahalı başka yöntem çağrıları, sadece temsilci yaratma sıkı bir döngü bu. biraz ekstra zaman.

Temsilci hiç bir sınıf için değişir bu yana, bu durumda, döngü ve daha verimli olacak kodu dışında oluşturabilirsiniz.

Güncelleme:

Aslında, benim makinede bile herhangi bir iyileştirme olmaksızın, serbest modunda derleme 5x fark görmüyorum. Ben sadece DateTime Alan Listesi 5.000 öğeleri ile olan Item üzerinde 1,000,000 aramalar yapıldı. Tabii ki, benim veri, vb, farklı, ama bu kez soyut temsilci zaman aslında çok yakın olduğunu görebilirsiniz:

yinelemeli : 14279, 0.014279 ms ms/Ara

seri w opt : 17400, 0.0174 ms ms/Ara

Bu zaman farkı vardırçokküçük ve okunabilirlik değer ve idame ETMENİZ kullanarak iyileştirmeler. Beni test koşum olarak görmediğimiz bir şey var inandırır 5x fark olsa da, göremiyorum.

Bunu Paylaş:
  • Google+
  • E-Posta
Etiketler:

YORUMLAR

SPONSOR VİDEO

Rastgele Yazarlar

  • Klemens Torggler

    Klemens Torg

    11 Mart 2008
  • Marques Brownlee

    Marques Brow

    21 Mart 2008
  • Orson Wang

    Orson Wang

    28 EKİM 2006