小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

蛙蛙推薦: LRU緩存的實現(xiàn)算法討論...

 Dawnxu 2009-07-23

業(yè)務模型

讀、寫、刪的比例大致是7:3:1,至少要支持500w條緩存,平均每條緩存6k,要求設計一套性能比較好的緩存算法。
算法分析

不考慮MemCached,Velocity等現(xiàn)成的key-value緩存方案,也不考慮脫離.net gc自己管理內(nèi)存,不考慮隨機讀取數(shù)據(jù)及順序讀取數(shù)據(jù)的場景,目前想到的有如下幾種LRU方案

算法

分析

SortedDictionary

.net自帶的,內(nèi)部用二叉搜索樹(應該不是普通樹,至少是做過優(yōu)化的樹)實現(xiàn)的,檢索為O(log n),比普通的DictionayO(1))慢一點。
插入和刪除都是O(log n),而且插入和刪除,會實時排序。
但是.net 2.0的這個類沒有First屬性

Dictionary + PriorityQueue

Dictionay可以保證檢索是O(1);
優(yōu)先隊列可以保證插入和刪除都為O(log n)
但是優(yōu)先隊列刪除指定的項不支持(至少我找到的優(yōu)先隊列不支持),所以在刪除緩存的時候不知道咋實現(xiàn)

Dictionay + Binary heap

二叉堆也是優(yōu)先隊列,分析應該同上,我沒有詳細評估。

b

查找,刪除,插入效率都很好,數(shù)據(jù)庫都用它,但實現(xiàn)復雜,寫一個沒有BUGB樹幾乎不可能。有人提到stl:map是自頂向下的紅黑樹,查找,刪除,插入都是O(log n),但咱不懂c++,沒做詳細測試。

Dictionay + List

Dict用來檢索;
List用來排序;
檢索、添加、刪除都沒問題,只有在清空的時候需要執(zhí)行List的排序方法,這時候緩存條目比較多的話,可能比較慢。

Dictionay + LinkedList

Dict用來檢索;
LinkedList的添加和刪除都是O(1),添加緩存時在鏈表頭加節(jié)點,獲取緩存時把特定節(jié)點移動(先刪除特定節(jié)點(O(n)),再到頭部添加節(jié)點(O(1)))到頭,緩存滿地時候截斷掉尾部的一些節(jié)點。

目前幾種方案在多線程下應該都需要加鎖,不太好設計無鎖的方案,下面這個鏈接是一個支持多線程的方案,但原理至今沒搞特明白
A High Performance Multi-Threaded LRU Cache
http://www./KB/recipes/LRUCache.aspx

用普通鏈表簡單實現(xiàn)LRU緩存
以下是最后一種方案的簡單實現(xiàn),大家討論下這個方案值不值得優(yōu)化,或者其它的哪個方案比較合適
 

public class LRUCacheHelper<K, V> {
    
readonly Dictionary<K, V> _dict;
    
readonly LinkedList<K> _queue = new LinkedList<K>();
    
readonly object _syncRoot = new object();
    
private readonly int _max;
    
public LRUCacheHelper(int capacity, int max) {
        _dict 
= new Dictionary<K, V>(capacity);
        _max 
= max;
    }
 
    
public void Add(K key, V value) {
        
lock (_syncRoot) {
            checkAndTruncate();
            _queue.AddFirst(key);   
//O(1)
            _dict[key] = value;     //O(1)
        }
    }
 
    
private void checkAndTruncate() {
        
lock (_syncRoot) {
            
int count = _dict.Count;                        //O(1)
            if (count >= _max) {
                
int needRemoveCount = count / 10;
                
for (int i = 0; i < needRemoveCount; i++) {
                    _dict.Remove(_queue.Last.Value);        
//O(1)
                    _queue.RemoveLast();                    //O(1)
                }
            }
        }
    }
 
    
public void Delete(K key) {
        
lock (_syncRoot) {
            _dict.Remove(key); 
//(1)
            _queue.Remove(key); // O(n)
        }
    }
    
public V Get(K key) {
        
lock (_syncRoot) {
            V ret;
            _dict.TryGetValue(key, 
out ret);    //O(1)
            _queue.Remove(key);                 //O(n)
            _queue.AddFirst(key);               //(1)
            return ret;
        }
    }
}


用雙頭鏈表代替普通鏈表

突然想起來了,可以把鏈表換成雙頭鏈表,然后在字典里保存鏈表節(jié)點,在Get方法的時候直接從字典里獲取到要移動的節(jié)點,然后把這個節(jié)點的上一個節(jié)點的Next指針指向給下一個節(jié)點,下一個節(jié)點的Previous指針指向上一個節(jié)點,這樣就把移動節(jié)點的操作簡化成O(1)了,提高了緩存讀取的效率。

_dict.TryGetValue(key, out ret);    //O(1)
ret.Next.Previous = ret.Previous     //O(1)
ret. Previous.Next. = ret.Next         //O(1)
  _queue.AddFirst(key);                      //O(1)

我改進后的鏈表就差不多滿足需求了,

操作

基本操作

復雜度

讀取

Dict.Get

Queue.Move

O 1

O 1

刪除

Dict.Remove

Queue.Remove

O 1

O 1

增加

Dict.Add

Queue.AddFirst

O 1

O 1

截斷

Dict.Remove

Queue.RemoveLast

O k

O k

K表示截斷緩存元素的個數(shù)

 

其中截斷的時候可以指定當緩存滿的時候截斷百分之多少的最少使用的緩存項。

其它的就是多線程的時候鎖再看看怎么優(yōu)化,字典有線程安全的版本,就把.net 3.0的讀寫鎖扣出來再把普通的泛型字典保證成ThreadSafelyDictionay就行了,性能應該挺好的。

鏈表的話不太好用讀寫鎖來做線程同步,大不了用互斥鎖,但得考慮下鎖的粒度,Move,AddFirst,RemoveLast的時候只操作一兩個節(jié)點,是不是想辦法只lock這幾個節(jié)點就行了,Truncate的時候因為要批量操作很多節(jié)點,所以要上個大多鏈表鎖,但這時候怎么讓其它操作停止得考慮考慮,類似數(shù)據(jù)庫的表鎖和行鎖。

實現(xiàn)代碼

 

public class DoubleLinkedListNode<T> {
    
public T Value { getset; }
 
    
public DoubleLinkedListNode<T> Next { getset; }
 
    
public DoubleLinkedListNode<T> Prior { getset; }
 
    
public DoubleLinkedListNode(T t) { Value = t; }
 
    
public DoubleLinkedListNode() { }
 
    
public void RemoveSelf() {
        Prior.Next 
= Next;
        Next.Prior 
= Prior;
    }
 
}
public class DoubleLinkedList<T> {
    
protected DoubleLinkedListNode<T> m_Head;
    
private DoubleLinkedListNode<T> m_Tail;
 
    
public DoubleLinkedList() {
        m_Head 
= new DoubleLinkedListNode<T>();
        m_Tail 
= m_Head;
    }
 
    
public DoubleLinkedList(T t)
        : 
this() {
        m_Head.Next 
= new DoubleLinkedListNode<T>(t);
        m_Tail 
= m_Head.Next;
        m_Tail.Prior 
= m_Head;
    }
 
    
public DoubleLinkedListNode<T> Tail {
        
get { return m_Tail; }
    }
 
    
public DoubleLinkedListNode<T> AddHead(T t) {
        DoubleLinkedListNode
<T> insertNode = new DoubleLinkedListNode<T>(t);
        DoubleLinkedListNode
<T> currentNode = m_Head;
        insertNode.Prior 
= null;
        insertNode.Next 
= currentNode;
        currentNode.Prior 
= insertNode;
        m_Head 
= insertNode;
        
return insertNode;
    }
    
public void RemoveTail() {
        m_Tail 
= m_Tail.Prior;
        m_Tail.Next 
= null;
        
return;
    }
}
public class LRUCacheHelper<K, V> {
    
class DictItem {
        
public DoubleLinkedListNode<K> Node { getset; }
        
public V Value { getset; }
    }
    
readonly Dictionary<K, DictItem> _dict;
    
readonly DoubleLinkedList<K> _queue = new DoubleLinkedList<K>();
    
readonly object _syncRoot = new object();
    
private readonly int _max;
    
public LRUCacheHelper(int capacity, int max) {
        _dict 
= new Dictionary<K, DictItem>(capacity);
        _max 
= max;
    }
 
    
public void Add(K key, V value) {
        
lock (this)
        {
 
            checkAndTruncate();
            DoubleLinkedListNode
<K> v = _queue.AddHead(key);   //O(1)
            _dict[key] = new DictItem() { Node = v, Value = value }; //O(1)
        }
    }
 
    
private void checkAndTruncate() {
        
int count = _dict.Count;                        //O(1)
        if (count >= _max) {
            
int needRemoveCount = count / 10;
            
for (int i = 0; i < needRemoveCount; i++) {
                _dict.Remove(_queue.Tail.Value);        
//O(1)
                _queue.RemoveTail();                    //O(1)
            }
        }
    }
 
    
public void Delete(K key) {
        
lock (this) {
            _dict[key].Node.RemoveSelf();
            _dict.Remove(key); 
//(1) 
        }
    }
    
public V Get(K key) {
        
lock (this) {
            DictItem ret;
            
if (_dict.TryGetValue(key, out ret)) {
                ret.Node.RemoveSelf();
                _queue.AddHead(key);
                
return ret.Value;
            }
            
return default(V); 
        }
    }
}


性能測試

用雙頭鏈表測試了一下,感覺性能還可以接受,每秒鐘讀取可達80w,每秒鐘寫操作越20w。

程序初始化200w條緩存,然后不斷的加,每加到500w,截斷掉10分之一,然后繼續(xù)加。

測試模型中每秒鐘的讀和寫的比例是7:3,以下是依次在3個時間點截取的性能計數(shù)器圖。
圖1

 
圖2

 

 
圖3

 


內(nèi)存最高會達到
1g,cpu也平均百分之90以上,但測試到后期會發(fā)現(xiàn)每隔一段時間,就會有一兩秒,吞吐量為0,如最后一張截圖,后來觀察發(fā)現(xiàn),停頓的那一兩秒是二代內(nèi)存在回收,等不停頓的時候# gen 2 collections就會加1,這個原因應該是鏈表引起的,對鏈表中節(jié)點的添加和刪除是很耗費GC的,因為會頻繁的創(chuàng)建和銷毀對象。 

后續(xù)改進

1、 用游標鏈表來代替普通的雙頭鏈表,程序起來就收工分配固定大小的數(shù)組,然后用數(shù)組的索引來做鏈表,省得每次添加和刪除節(jié)點都要GC的參與,這相當于手工管理內(nèi)存了,但目前我還沒找到c#合適的實現(xiàn)。

2、 有人說鏈表不適合用在多線程環(huán)境中,因為對鏈表的每個操作都要加互斥鎖,連讀寫鎖都用不上,我目前的實現(xiàn)是直接用互斥鎖做的線程同步,每秒的吞吐量七八十萬,感覺lock也不是瓶頸,如果要改進的話可以把DictionaryThreadSafelyDictionary來代替,然后鏈表還用互斥鎖(剛開始設想的鏈表操作只鎖要操作的幾個節(jié)點以降低并發(fā)沖突的想法應該不可取,不嚴謹)。

3、 還有一個地方就是把鎖細分以下,鏈表還用鏈表,但每個鏈表的節(jié)點是個HashSet,對HashSet的操作如果只有讀,寫,刪,沒有遍歷的話應該不需要做線程同步(我感覺不用,因為Set就是一個集合,一個線程往里插入,一個線程往里刪除,一個線程讀取應該沒問題,頂多讀進來的數(shù)據(jù)可能馬上就刪除了,而整個Set的結(jié)構(gòu)不會破壞)。然后新增數(shù)據(jù)的時候往鏈表頭頂Set里插入,讀取某個數(shù)據(jù)的時候把它所在的節(jié)點的Set里刪除該數(shù)據(jù),然后再鏈表頭的Set里插入一個數(shù)據(jù),這樣反復操作后,鏈表的最后一個節(jié)點的Set里的數(shù)據(jù)都是舊數(shù)據(jù)了,可以安全的刪除了,當然這個刪除的時候應該是要鎖整個鏈表的。每個Set應該有個大小上限,比如20w,但set不能安全的遍歷,就不能得到當前大小,所以添加、刪除Set的數(shù)據(jù)的時候應該用Interlocked.Decrement() Interlocked.Increment()維護一個Count,一遍一個Set滿的時候,再到鏈表的頭新增一個Set節(jié)點。

性能測試腳本

 

class Program {
    
private static PerformanceCounter _addCounter;
    
private static PerformanceCounter _getCounter;
 
    
static void Main(string[] args) {
        SetupCategory();
        _addCounter 
= new PerformanceCounter("wawasoft.lrucache""add/sec"false);
        _getCounter 
= new PerformanceCounter("wawasoft.lrucache""get/sec"false);
        _addCounter.RawValue 
= 0;
        _getCounter.RawValue 
= 0;
 
        Random rnd 
= new Random();
        
const int max = 500 * 10000;
 
 
        LRUCacheHelper
<intint> cache = new LRUCacheHelper<intint>(200 * 10000, max);
 
        
for (int i = 10000*100000 - 1; i >= 0; i--)
        {
            
if(i % 10 > 7)
            {
                ThreadPool.QueueUserWorkItem(
                    
delegate
                        {
                            cache.Add(rnd.Next(
010000), 0);
                            _addCounter.Increment(); 
                        });
            }
            
else
            {
                ThreadPool.QueueUserWorkItem(
                   
delegate
                   {
                       
int pop = cache.Get(i);
                       _getCounter.Increment();
                   });
            }
        }
        Console.ReadKey();
    }
 
    
private static void SetupCategory() {
        
if (!PerformanceCounterCategory.Exists("wawasoft.lrucache")) {
 
            CounterCreationDataCollection CCDC 
= new CounterCreationDataCollection();
 
            CounterCreationData addcounter 
= new CounterCreationData();
            addcounter.CounterType 
= PerformanceCounterType.RateOfCountsPerSecond32;
            addcounter.CounterName 
= "add/sec";
            CCDC.Add(addcounter);
 
 
            CounterCreationData getcounter 
= new CounterCreationData();
            getcounter.CounterType 
= PerformanceCounterType.RateOfCountsPerSecond32;
            getcounter.CounterName 
= "get/sec";
            CCDC.Add(getcounter);
 
            PerformanceCounterCategory.Create(
"wawasoft.lrucache","lrucache",CCDC);
 
        }
    }
 
}


參考鏈接

不分先后,都是隨時從網(wǎng)上找的,大多看不懂
潛心學習數(shù)據(jù)結(jié)構(gòu)-C#語言描述系列文章
http://space.cnblogs.com/group/topic/6922/
《C++數(shù)據(jù)結(jié)構(gòu)原理與經(jīng)典問題求解》
http://www./itbook/bookinfodetail.asp?lbbh=10087298&sort=ml
數(shù)據(jù)結(jié)構(gòu)(C#):循環(huán)鏈表
http://www.cnblogs.com/zxjay/archive/2008/12/07/1349688.html
表的游標實現(xiàn)
http://www.comp./~xujia/mirror/algorithm.myrice.com/datastructure/basic/list/chapter3_3.htm
我所理解的鏈表1
http://www./course/3_program/c/csuanfa/2007213/21570.html
cursor implementation of linked list
http://wiki./Q/Explain_the_Cursor_implementation_of_linked_list
Sorting Algorithms In C#
http://www./KB/recipes/cssorters.aspx
The C5 Generic Collection Library
http://www./research/c5/
當弱引用對象成為集合元素時
http://www./post/Coding/Weakreference-Collection-CSharp.html
QuickSort in Functional C#
http://blogs./cs/blogs/jlee/archive/2008/05/23/quicksort-in-functional-c.aspx
LRU頁面置換算法模擬
http://dev.csdn.net/article/73207.shtm

    本站是提供個人知識管理的網(wǎng)絡存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多