LRU-K,2Q,LIRS算法介紹與比較

    研究H2的過程中發現新的存儲引擎MVStore使用了新的cache替換算法——LIRS,經過一系列相關的論文研讀,發現比舊存儲引擎PageStore的LRU算法改良不少。爲了更好地瞭解LIRS的優異性,把同樣屬於LRU變種的基於倒數第二次訪問時間對比進行cache替換的LRU-K(K一般爲2)[1],2Q[2],LIRS[3]算法進行對比

概述

    爲方便討論,統一稱呼要進行緩存的對象爲塊(或Page)。在訪問塊的行爲中,假定存在時間局部性原理,(temporal locality - locations referenced recently likely to be referenced again)。cache替換算法就是針對局部性原理,分辨哪些是訪問頻率高的hot塊,哪些是訪問頻率低的cold塊,並緩存hot塊到cache中,從而提高cache命中率。但對於現實中的數據存在不同的訪問規律,因此cache算法爲了必須儘快地適應塊訪問規律的改變,緩存新的hot塊,並同時避免cold塊“污染”hot塊的緩存。

    論文[3]提出了4種塊訪問規律:

1.順序訪問。所有的塊一個接一個被訪問,不存在重訪問。

2.循環訪問。所有塊都按照一定的間隔重複訪問

3.時間密集訪問。最近被訪問的塊是將來最有可能被訪問的。

4.概率訪問。所有塊都有固定的訪問概率,所有塊都互相獨立地根據概率被訪問。

    論文[1]提出了2個訪問規律中出現的問題:

5.Correlated References。關聯訪問,即塊被首次訪問之後,緊接着的短時間內會有數次訪問。

6.Reference Retained Information Problem。訪問信息保存問題。即需要在塊替換出cache後,仍然保留之前的訪問信息。

具體算法

    由於傳統的LRU算法存在較多的問題,如順序塊訪問會把hot塊替換出cache,對於索引塊和數據塊的循環訪問時,不會根據訪問概率緩存索引塊。LRU-K,2Q,LIRS等cache替換算法就是爲了解決LRU算法的問題,提供同樣甚至更高性能的同時,同時不需要外部的調控,能夠自動根據塊訪問規律的改變對cache進行調整,都是作爲通用的塊緩存算法。

LRU-K

    K指的是最後第K次訪問的距離,也就是倒數第K次訪問時和最近一次訪問的時間差。LRU-K算法主要是對比最後第K次的訪問距離,訪問距離越大則代表每次的訪問間隔越長,因此更容易被替換出cahce。另外論文[1]中提出了對於穩定不變的訪問規律,K越大,cache命中率會越高,但對於訪問規律變化較大的時候,K越大則表明需要更加多的訪問去適應新的規律,因此變化響應更差,因此一般取K=2。
    原論文考慮到訪問規律出現5,6中的問題,提出了Correlated References Period和Reference Retained Information Period兩個時間間隔參數。
    Correlated References Period,指塊首次訪問後的一段時間。塊(可能是cold或者hot)的首次訪問後可能會接着數次短時間內的關聯訪問,如數據庫中同一事務內的select和update會多次掃描相同的塊,爲了避免關聯訪問的干擾造成對塊的錯誤判斷,在第一次訪問塊後,會預留在cahce中。在這段時間內的多次訪問只算作一次訪問。只有這段時間後塊再次被訪問,纔算第二次被訪問。
    Reference Retained Information Period,則指塊被替換出cache後的一段時間。塊被替換出cache後,可能很快地再次被訪問,由於之前訪問記錄已丟棄,這樣只算作首次訪問,之後又很快被替換出cahce後,又再次被訪問,這樣又只會算作首次訪問,如此下來,雖然塊被頻繁訪問,屬於hot塊,但由於替換出cahce後沒有保留訪問信息,導致錯誤判斷。因此對於替換出cache後的塊會繼續保留訪問信息一段時間。
    由於原論文只給出僞代碼,並沒有具體的實現。雖然網絡上有各種的LRU-K的實現,但某些如多個LRU棧組合的實現並不符合論文的思路。因此結合以上的討論,個人總結了一個改進後的簡單實現(K=2):

  • LRU隊列A1。第一次訪問的塊分配cache後,插入A1隊列尾部。在A1中的塊被訪問時,重新加入隊列A1尾部。A1頭部出列的塊則插入優先級隊列P(倒數第二次訪問時間初始化爲0)。該隊列主要實現Correlated References Period,需要根據實際情況設置隊列合理固定大小。
  • 優先級隊列P。優先級隊列P以倒數第二次的訪問時間進行升序排序。只有當從A1出列的塊或者A2重新訪問的塊可以插入隊列P。P中的塊被訪問時,更新倒數第二次訪問時間並重新排序。當需要分配cache的時候,P隊列頭部的塊(倒數第二次訪問時間最短,也就是距離最大)替換出cache後插入到A2中。
  • FIFO隊列A2。負責保存替換出cache的塊訪問信息。如果A2中的塊再次被訪問,就更新倒數第二次訪問時間,同時分配cache,插入優先級隊列P。塊從A2出列則刪除其歷史訪問信息。
  • [可選]使用HashMap保存塊的特證鍵值和對應的塊訪問信息,加快查找速度。

總結
   
以上實現中,總共有3個隊列,A1,A2,P。其中cache分配給在A1和P和的塊,P所佔cache比例較大。A2只保存塊的訪問信息。塊的訪問信息包含倒數第二次訪問時間,最後一次訪問時間等。如果擴展到K,則只需要通過保存K次的訪問時間,同時初始化爲0即可。
    LRU-K對於LRU的改進,最主要是採用了更爲激進的方法去替換cold塊出cache,這樣能夠較好地避免順序訪問對cache的影響以及能夠更好地區分塊訪問的頻率,但同時,LRU-K算法中存在一些問題:
    1.由於優先級隊列的排序操作需要額外的O(logN)的時間複雜度,N爲P的大小。
    2.A1,P和A2的大小都必須按照實際情況進行配置取最優比例,才能發揮最優性能。
    3.塊的訪問頻率變化響應較慢。這是因爲P的比較是按照歷史的最後第K次訪問距離進行比較。如果塊A在P中的時候倒數第K次的距離較少,但經過較長時間纔有新的訪問,重新更新訪問距離後,纔會被快速替換出cache。

2Q

    2Q指的是Two Queue,就是依靠兩個隊列實現的cache替換算法。針對LRU-K算法的O(logN)時間複雜度,2Q目的是實現O(1)時間複雜度,不需要設置額外參數,並且性能等同甚至優於後者的通用cache替換算法。另外2Q算法也同樣解決了LRU算法中的限制,即順序訪問,以及索引塊和數據塊循環訪問的問題。
    論文[2]中首先提出了簡化的實現方法:

  • FIFO隊列A1。塊首次被訪問時,分配cache,插入隊列A1的隊尾。
  • LRU隊列Am。塊在A1中再次被訪問時,就會加入到Am的隊尾。
    分配cache時,如果cache沒有空閒,首先A1超過閾值時,就會刪除A1的頭部,否則刪除Am的頭部。
    簡化的實現中,A1和Am各自所佔cache的比例是關鍵。如果A1太小,則檢測是否hot塊的時間太短,很可能需要較長時間才把hot塊加入到Am中。但如果A1太大,則A1會佔了原本所屬Am的cache,hot塊的數量就會減少,會影響cache命中率。
    爲了解決上述問題,論文提出了2Q的完整實現,主要是把A1分割爲A1in,A1out兩個隊列:
  • FIFO隊列A1in。首次被訪問的塊分配cache後,插入A1in隊尾。A1in的塊被訪問後不做任何動作。A1in隊列頭部出列後,替換出cache並插入塊指針到A1out。A1in類似LRU-K中的A1,實現Correlated References Period,但A1in中的塊被訪問時不會重新插入隊尾。
  • FIFO隊列A1out。A1in隊列頭部出列後的塊,只有塊指針會插入到A1out隊尾。A1out的塊被訪問後,分配cache並插入到Am隊列隊尾。A1out隊列頭部出列後,塊指針被刪除。
  • LRU隊列Am。A1out中的塊被訪問後,分配cache並插入Am隊尾。Am中的塊被訪問後,重新插入Am隊尾。Am隊列頭部出列後,塊替換出cache,相關信息被刪除。
    分配cache時,如果cache沒有空閒,如果A1in超出Kin閾值,A1in隊列頭部塊出列,替換出cache後插入A1out隊尾,如果A1out超過Kout閾值,A1out隊列頭部塊出列並刪除塊指針;否則就把Am隊列頭部的塊出列,替換出cache。

總結

    可以看到,和LRU-K比較最後K次訪問距離,快速替換出cache中cold塊相比,2Q通過對比Am的最近訪問時間,替換塊出cache,目的是使hot塊能常駐在cache中。另外要注意到A1in和A1out兩個隊列的作用,A1in主要是作爲Correlated References Period的實現,而A1out則是需要分辨hot塊和cold塊,在測試中發現A1in的塊適合分配cache,A1out的塊則更適合分配塊指針。2Q對比LRU-K,只需要記錄更少的信息,更少參數配置(推薦Kin爲25%,Kout爲50%),以及更低的時間複雜度O(1)。
    2Q算法中的缺點:
    1.仍然需要配置參數。A1in和A1out的大小閾值Kin和Kout的需要根據實際進行配置。
    2.Kout固定值。Kout的大小主要影響訪問模式變化的響應速度,Kout爲固定值則不能根據塊訪問模式變化而動態變化。

    3.Belady’s anomaly:cache大小增加反而導致cache命中率下降[3]。

LIRS

    LIRS,Low Inter-reference Recency Set,主要通過比較IRR(Inter-Reference Recency )來決定哪些塊被替換出cache。LIRS也是目標實現一個低開銷,不需要額外參數設置,並且性能優異於其它同類型的cache替換算法。
    首先要了解一下LIRS的兩個概念:
recency,最近被訪問的時間。
Inter-Reference Recency (IRR),同一塊連續兩次訪問期間中間訪問過的不重複塊數。IRR用於記錄塊的歷史信息,假定IRR值大的塊,其值接下來也會大,也就是訪問頻率低。因此選擇IRR大的塊進行replacement,但要注意這些塊的recency可能會比較低,也就是可能是最近才被訪問的塊。
    LIRS算法動態區分低IRR(LIR)和高IRR(HIR)的塊,LIR塊一般會常駐cache,HIR塊則會較快被替換出cache。要保證所有LIR塊都能緩存,只有比例較小的cache供HIR塊緩存,當LIR塊的recency超過某個值,HIR塊在一個更小的recency中被訪問,兩者的狀態就會交換。
    論文給出了詳細的實現:
Stack S:  包括LIR塊、少於LIR塊最大recency的HIR塊(包括已經緩存或者沒有緩存)
Queue Q:  HIR塊緩存隊列,FIFO

  • 棧S大小一般沒有限制,包含LIR塊和HIR塊的entry,entry記錄了塊的LIR/HIR狀態,是否駐cache(LIR一定駐cache,HIR不一定)。爲了加快HIR塊緩存的搜索,隊列Q負責連接HIR塊的緩存,size爲HIR塊分配的緩存。當需要釋放緩存時,會先刪除隊列Q的頭部的HIR塊緩存,這時如果HIR塊仍然在棧S,則轉換狀態爲非駐cache。
  • 確保棧S的底部必須爲LIR塊,定義“棧裁剪”操作,棧S的底部LIR塊被刪除,則一直刪除底部塊直到遇到另一個LIR塊。這樣做的目的是因爲如果底部存在HIR塊,則這些HIR塊必定大於LIR塊的最大recency,這樣它們肯定不能轉變爲LIR塊。
  • 如果在棧S中的HIR塊被訪問,則它的IRR,就是未訪問前的recency,必定少於位於底部的LIR塊的recency,也就是最大recency的LIR塊,因此HIR塊轉換爲LIR塊,底部的LIR塊則轉換爲HIR塊,並同時從棧S刪除,添加到隊列Q的尾部。
  • LIR塊緩存沒滿時,所有首次訪問塊都作爲LIR狀態,並駐cache中,直到超出LIR塊緩存閾值後,首次訪問塊會被賦予HIR塊狀態。另外,棧S出棧的塊都會轉換爲HIR狀態。
    LIRS算法對於不同類型的塊訪問的做法如下:
  • 訪問棧S中的LIR塊X:LIR塊必定駐cache中,所以必定命中緩存。然後把塊X移動到棧S的頭部,如果塊X之前是在棧S的底部,則執行“棧裁剪”操作。
  • 訪問駐cache中的HIR塊X:訪問命中緩存。把X移動到棧S頭部。另外塊X有兩種情況:(1)塊X在棧S中,把它狀態轉換爲LIR,還刪除隊列Q中塊X的cache。然後把棧S底部的LIR塊轉換爲HIR塊,然後移動到隊列Q中。最後“棧裁剪”。(2)塊X不在棧S中,則塊X的狀態保持HIR不變,然後從隊列Q的cache移動到隊列尾部。
  • 訪問非駐cache中的HIR塊X:沒有命中緩存。首先刪除隊列Q頭部的HIR塊(如果該塊在棧S,則變爲非駐cache狀態),這樣多出cache空間,然後加載塊X到該cache空間,然後移動到棧S的頂部。塊X同樣有兩種情況:(1)塊X在棧S中,改變狀態爲LIR,並同時改變棧底部的LIR塊爲HIR塊,並移動到隊列Q的尾部,然後“棧裁剪”。(2)塊X不在棧S中,則狀態爲HIR,並放到隊列Q的尾部。
    在上述算法中,與2Q進行對比,可以看到LIRS巧妙地把棧S作爲A1in,A1out,Am的合併,通過對比塊的recency從而判斷IRR大小來決定塊屬於hot塊,需要常駐cache中。另外,隊列Q也解決了Reference Retained Information的問題,棧S出棧的塊會重新加入隊列Q一段時間。不過論文的作者顯然沒有考慮Correlated References
的問題,如果某些塊在短時間內產生數次關聯訪問,則很快變爲LIR塊駐cache中。
    LIRS對於上面提到的4種訪問模式能夠快速適應。特別地,對於循環訪問,LIRS能夠固定開始的LIR塊駐cache中,保證一定的cache命中率,這點比LRU-K以及2Q要好。另外LIRS不像2Q需要設置過多參數,通常假設LIR佔99%的cache大小,HIR佔1%即可。
存在問題:
    1.對於順序訪問的塊,即會出現大量第一次訪問塊,由於棧S沒有考慮到entry大小的限制,因此會一直添加這些順序訪問塊到棧S的頭部,使棧S變得很大。改良方法是,給棧S一個大小限制,超過的時候就去刪除最接近底部的那些HIR塊,這個大小可以是cache的幾倍,經過測試不會造成太大的性能影響,另外棧S記錄的信息只有幾byte,棧S大小超過cache大小几倍不是很大問題。
    2."棧裁剪"操作只是平均的O(1)時間複雜度,並不是最差O(1)時間複雜度。

    3.對於IRR變化不會太敏感。如某些cold塊IRR瞬間變小,變成LIR塊,這樣會把棧S底部的LIR塊變爲HIR塊,從而很快被替換出cache,這樣就造成後面的cache miss

總結

    LRU-K,2Q,LIRS三種算法都基於倒數第二次的訪問時間,以此推斷塊的訪問頻率,從而替換出訪問頻率低的塊。從空間額外消耗來看,除了LRU-K需要記錄訪問時間外,LIRS需要記錄塊狀態(HIR/LIR等),2Q並不需要太多的訪問信息記錄,因此2Q>LIRS>LRU-K。從時間複雜度來看,LRU-K是O(logN),2Q和LIRS都是O(1),但LIRS的"棧裁剪"是平均的O(1),因此2Q>LIRS>LRU-K。從實現複雜來看,LIRS只需要兩個隊列,2Q和LRU-K的完整實現都需要3個隊列,因此LIRS>2Q=LRU-K。最後,LIRS是唯一參數不需要去按照實際情況進行調整(儘管仍然有LIR和HIR的cache大小參數),2Q和LRU-K都需要進行細微的參數調整,因此LIRS>2Q=LRU-K。從性能角度來看,LIRS論文看得出還是有一定的提升,LIRS>2Q>LRU-K。
    本文目前只比較了三種LRU變種算法,事實上,還有基於業務情況,基於訪問模式探測等不同類型的cache替換算法。另外對於LRU變種算法中,ARC也是值得探索的。我們應該明白並不存在萬能的cache替換算法可以適用於任何情況。事實上,在真實database應用中,一般會對論文中的算法做適當的調整和擴展,使其更適用自身,能夠發揮最佳性能。

Reference

[1]E. J. O’Neil, P. E. O’Neil, and G. Weikum, “The LRU-K Page Replacement Algorithm for Database Disk Buffering”

[2]T. Johnson and D. Shasha, “2Q: A Low Overhead High Performance Buffer Management Replacement Algorithm”

[3]Song Jiang and Xiaodong Zhang, "LIRS: An Efficient Low Inter-reference Recency Set Replacement Policy to Improve Buffer Cache Performance"

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章