Bu önbellek stratejisi için hangi veri yapısını kullanmalıyım?


11

Ben bir çift dönen iki çift üzerinde oldukça pahalı bir hesaplama gerçekleştiren bir .NET 4.0 uygulaması üzerinde çalışıyorum. Bu hesaplama birkaç bin maddeden her biri için yapılır . Bu hesaplamalar Taskbir threadpool iş parçacığında bir yapılır.

Bazı ön testler, aynı hesaplamaların tekrar tekrar yapıldığını göstermiştir, bu nedenle n sonuçlarını önbelleğe almak istiyorum . Önbellek dolduğunda, ben en az önemlisi dışarı atmak istiyorum sıklıkla son kullanılan öğeyi. ( Düzenleme: En azından çoğu zaman mantıklı gelmediğini fark ettim, çünkü önbellek dolu olduğunda ve bir sonucu yeni hesaplanmış bir sonuçla değiştirdiğimde, en az sıklıkla kullanılır ve bir sonraki yeni sonuç hesaplandığında hemen değiştirilir ve önbelleğe eklendi)

Bunu uygulamak için, ben kullanmayı düşünüyorum Dictionary<Input, double>( Inputgirdileri ve önbelleğe alınan sonuçlar saklamak için iki giriş çift değerleri depolamak olacak bir mini sınıf). Ancak, bir sonucun en son ne zaman kullanıldığını da izlemem gerekir. Bunun için önbellek doluyken dikdörtgenden bir sonucu çıkarmak için ihtiyacım olan bilgileri saklayan ikinci bir koleksiyona ihtiyacım olduğunu düşünüyorum. Bu listeyi sürekli tutmanın performansı olumsuz etkileyeceğinden endişe duyuyorum.

Bunu yapmanın daha iyi (yani daha performanslı) bir yolu var mı, belki de farkında olmadığım ortak bir veri yapısı var mı? Çözümümün en uygunluğunu belirlemek için ne tür şeyler profillemeliyim / ölçmeliyim?

Yanıtlar:


12

LRU tahliye önbelleğini (En Son Kullanılan Boşaltma) kullanmak istiyorsanız, muhtemelen kullanılacak veri yapılarının iyi bir kombinasyonu:

  • Dairesel bağlantılı liste (öncelik sırası olarak)
  • Sözlük

Bu nedenle:

  • Bağlantılı listenin O (1) yerleştirme ve çıkarma süresi vardır
  • Liste dolduğunda ve ekstra ayırma yapılmasına gerek olmadığında liste düğümleri yeniden kullanılabilir.

Temel algoritma şu şekilde çalışmalıdır:

Veri yapıları

LinkedList<Node<KeyValuePair<Input,Double>>> list; Dictionary<Input,Node<KeyValuePair<Input,Double>>> dict;

  1. Giriş alındı
  2. Sözlük anahtar içeriyorsa
    • düğüme kaydedilen değeri döndürün ve düğümü listenin başına taşıyın
  3. Sözlük anahtar içermiyorsa
    • değeri hesapla
    • değeri listenin son düğümünde sakla
    • sonuncunun bir değeri yoksa, önceki anahtarı sözlükten kaldırın
    • son düğümü ilk konuma getirin.
    • (girdi, düğüm) anahtar / değer çiftini sözlükte saklayın.

Bu yaklaşımın bazı faydaları, sözlük değeri O (1) 'i okumak ve ayarlamak, bağlantılı bir listeye düğüm eklemek ve çıkarmak O (1)' dir, yani algoritma değerlerin okunması ve yazılması için O (1) 'e yaklaşır. bellek ayırmalarını önler ve bellek kopyalama işlemlerini engelleyerek bellek açısından kararlı olmasını sağlar.


İyi noktalar, şimdiye kadarki en iyi fikir, IMHO. Bugün buna dayalı bir önbellek uyguladım ve yarın ne kadar iyi performans gösterdiğini görmek ve görmek zorunda kalacak.
PersonalNexus

3

Bu, ortalama bir PC'de elinizin altında bulunan işlem gücü göz önüne alındığında, tek bir hesaplama yapmak için çok çaba sarf ediyor gibi görünüyor. Ayrıca, her bir benzersiz değer çifti için hesaplamanıza yapılan ilk çağrının masrafını almaya devam edersiniz, bu nedenle 100.000 benzersiz değer çifti size minimum olarak n * 100.000 Zamanına mal olur . Sözlüğünüzdeki değerlere erişmenin, sözlük büyüdükçe muhtemelen yavaşlayacağını düşünün. Sözlük erişim hızınızın, hesaplama hızınıza karşı makul bir getiri sağlayacak kadar telafi edeceğini garanti edebilir misiniz?

Ne olursa olsun, muhtemelen algoritmanızı optimize etmek için bir yol bulmayı düşünmeniz gerekecek gibi görünüyor. Bunun için , darboğazların nerede olduğunu görmek ve sınıf örneklemeleri, liste geçişleri, veritabanı ile ilgili bazı ek yükleri azaltmanın yolları olup olmadığını belirlemenize yardımcı olmak için Redgate Karıncalar gibi bir profil oluşturma aracına ihtiyacınız olacak. erişir, ya da ne olursa olsun size çok zaman harcar.


1
Ne yazık ki, şu anda hesaplama algoritması değiştirilemez, çünkü doğal olarak CPU yoğun olan bazı gelişmiş matematik kullanan bir üçüncü taraf kütüphanesi. Daha sonra elden geçirilecekse, önerilen profilleme araçlarını kesinlikle kontrol edeceğim. Dahası, hesaplama oldukça sık, bazen aynı girdilerle gerçekleştirilecektir, bu nedenle ön profil oluşturma, çok naif bir önbellekleme stratejisiyle bile açık bir fayda göstermiştir.
PersonalNexus

0

Bir düşünce neden sadece önbellek n sonuçları? N 300.000 olsa bile, yalnızca 7.2MB bellek kullanırsınız (ayrıca tablo yapısı için ekstra her şeyi kullanırsınız). Tabii ki bu üç 64 bit iki katına çıkar. Bellek alanınızın bitmesinden endişe etmiyorsanız, karmaşık hesaplama rutininin kendisine not yazabilirsiniz.


Sadece bir önbellek olmayacak, ama analiz ettiğim her "öğe" için bir tane olacak ve bu öğelerden birkaç yüz bin olabilir.
PersonalNexus

Girdinin hangi 'Öğe'den geldiği önemli mi? yan etkileri var mı?
jk.

@jk. Farklı kalemler hesaplamaya çok farklı girdiler üretecektir. Bu, çok az çakışma olacağı anlamına geldiğinden, onları tek bir önbellekte tutmanın mantıklı olduğunu düşünmüyorum. Ayrıca, farklı öğeler farklı iş parçacıklarında yaşayabilir, bu nedenle paylaşılan durumdan kaçınmak için önbellekleri ayrı tutmak istiyorum.
PersonalNexus

@PersonalNexus Hesaplamaya katılan 2'den fazla parametre olduğunu ima etmek için bunu alıyorum? Yine de, temelde f (x, y) = bazı şeyler yapıyorsunuz. Artı paylaşılan devlet engel olmaktan ziyade performansa yardımcı olacak gibi görünüyor?
Peter Smith

@PeterSmith İki parametre ana girişlerdir. Başkaları da var, ama nadiren değişiyorlar. Eğer yaparlarsa, önbelleğin tamamını atacağım. "Paylaşılan durum" ile, tüm öğeler veya bir grup öğe için paylaşılan bir önbellek demek istedim. Bunun başka bir şekilde kilitlenmesi veya senkronize edilmesi gerektiğinden, performansı engelleyecektir. Paylaşılan devletin performans sonuçları hakkında daha fazla bilgi .
PersonalNexus

0

İkinci koleksiyonla yaklaşım iyidir. Min değerlerini hızlı bir şekilde bulmayı / silmeyi ve ayrıca kuyruktaki öncelikleri değiştirmeyi (arttırmayı) sağlayan bir öncelik kuyruğu olmalıdır (ikinci kısım zor olan, çoğu basit prio kuyruğu uygulaması tarafından desteklenmez). C5 kütüphane denir, böyle bir koleksiyona sahiptir IntervalHeap.

Veya tabii ki, kendi koleksiyonunuzu oluşturmaya çalışabilirsiniz, a SortedDictionary<int, List<InputCount>>. ( verilerinizi değerinizle InputCountbirleştiren sınıf olmalıdır )InputCount

Sayım değerinizi değiştirirken bu koleksiyonun güncellenmesi, bir öğe kaldırılıp yeniden eklenerek uygulanabilir.


0

Peter Smith'in cevabında belirtildiği gibi, uygulamaya çalıştığınız kalıba hafızalaştırma denir . C # 'da, yan etkileri olmadan notu şeffaf bir şekilde uygulamak oldukça zordur. Oliver Sturm'un C # içindeki fonksiyonel programlama kitabı bir çözüm sunar (kod indirilebilir, bölüm 10).

F # 'da çok daha kolay olurdu. Tabii ki, başka bir programlama dili kullanmaya başlamak büyük bir karardır, ancak dikkate değer olabilir. Özellikle karmaşık hesaplamalarda, programlamanın hafızadan daha fazla şeyi kolaylaştırması gerekir.

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.