欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

LRU?LFU?TinyLFU緩存算法實例詳解

 更新時間:2022年09月08日 10:10:18   作者:wonderstone  
這篇文章主要為大家介紹了LRU?LFU?TinyLFU緩存算法實例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

簡介

前置知識

知道什么是緩存

聽完本節(jié)公開課,你可以收獲

  • 掌握樸素LRU、LFU算法的思想以及源碼
  • 掌握一種流式計數(shù)的算法 Count-Min Sketch
  • 手撕TinyLFU算法、分析Window-TinyLFU源碼

一、LRU和LFU算法

LRU算法

LRU Least Recently Used 最近最少使用算法

LRU 算法的思想是如果一個數(shù)據在最近一段時間沒有被訪問到,那么在將來它被訪問的可能性也很小。所以,當指定的空間已存滿數(shù)據時,應當把最久沒有被訪問到的數(shù)據淘汰。

也就是淘汰數(shù)據的時候,只看數(shù)據在緩存里面待的時間長短這個維度。

這樣子做有什么缺點呢?我們來看個例子

無法復制加載中的內容

按照LRU算法進行訪問和數(shù)據淘汰,10次訪問的結果如下圖所示

無法復制加載中的內容

10次訪問結束后,緩存中剩下的數(shù)據是b、c、d三個元素,這個顯然不太合理。

直觀上講,為什么說他不合理,是因為明明a是被頻繁訪問的數(shù)據,最終卻被淘汰掉了。所以如果要改進這個算法,我們希望的是能夠記錄每個元素的訪問頻率信息,訪問頻率最低的那個才是最應該被淘汰的那個。

恭喜你,這就是LFU的規(guī)則。

在開始LFU之前,我們先來看一下LRU的代碼怎么寫。

有句古話講得好:緩存就是Map + 淘汰策略。Map的作用是提供快速訪問,淘汰策略是緩存算法的靈魂,決定了命中率的高低。根據對于LRU的描述,我們需要一個東西(術語叫做數(shù)據結構)來記錄數(shù)據被訪問的先后順序,這里我們可以選擇鏈表。

打開IDE,迅速寫下第一行代碼:

type LRU struct {
   data map[string]*list.Element
   cap int
   list *list.List
}

解釋一下為什么需要這幾個變量, cap 是緩存中可以存放的數(shù)據個數(shù),也就是緩存的容量上限。data就是Map。List我們用來記錄數(shù)據的先后訪問順序,每次訪問,都把本次訪問的節(jié)點移動到鏈表中的頭部。這樣子整個鏈表就會按照近期的訪問記錄來排序了。

func (lru *LRU) add(k, v string) {
   if Map中存有這條Key {
      替換Map中的Value值
      將鏈表中的對應節(jié)點移到最前面
   } else {
      if 已經達到緩存容量上限 {
         獲取鏈表尾部節(jié)點的Key,并從Map中刪除
         移除鏈表尾部的Node
      }
      創(chuàng)建要插入的新節(jié)點
      將新節(jié)點插入到鏈表頭部
      放入Map中
   }
}
func (lru *LRU) get(k string) string {
   if Map中存有這條Key {
      返回查詢到的Value
      將對應節(jié)點移動到鏈表頭部
   } else {
      返回 空
   }
}

LFU算法

我們已經成功的寫出了LRU算法(偽代碼),接下來嘗試自己寫一下LFU算法。首先我們知道LFU算法比LRU多了什么,LFU需要記錄每條數(shù)據的訪問次數(shù)信息,并且按照訪問次數(shù)從高到低排序,訪問次數(shù)用什么來記錄呢?

只需要在鏈表節(jié)點中增加一個訪問頻率Frequency,就可以了,這個Frequency可以使用int來存儲。同時排序的規(guī)則稍加變動,不是把最近訪問的放到最前面,而是按照訪問頻率插入到對應位置即可。如果頻率相同,再按照LRU的規(guī)則,比較誰是最新訪問的。

暫時無法在文檔外展示此內容

小結:

講完了LRU和LFU,我們來看一下他們有啥優(yōu)缺點。

LRU

優(yōu)點:實現(xiàn)簡單、可以很快的適應訪問模式的改變

缺點:對于熱點數(shù)據的命中率可能不如LFU

LFU

優(yōu)點:對于熱點數(shù)據命中率更高

缺點:難以應對突發(fā)的稀疏流量、可能存在舊數(shù)據長期不被淘汰,會影響某些場景下的命中率(如外賣),需要額外消耗來記錄和更新訪問頻率

二、TinyLFU

Count-Min Sketch 算法

剛才提到了LFU需要統(tǒng)計每個條數(shù)據的訪問頻率,這就需要一個int或者long類型來存儲次數(shù),但是仔細一想,一條緩存數(shù)據的訪問次數(shù)真的需要int類型這么大的表示范圍來統(tǒng)計嗎?我們認為一個緩存被訪問15次已經算是很高的頻率了,那么我們只用4個Bit就可以保存這個數(shù)據。(2^4=16)

再來介紹一個cmSketch算法,看過硬核課堂BloomFilter視頻的都知道,BloomFilter利用位圖的思想來標記一條數(shù)據是否存在,存在與否可以用某個Bit位的0 | 1來代替,那么我們能不能擴展一下,利用這種思想來計數(shù)呢?

我們給要計數(shù)的值計算一個Hash,然后在位圖中給這個Hash值對應的位置累加1就可以了,但是BloomFilter中的一個典型問題是假陽性,可以說只要是用Hash計算就有存在沖突的可能,那么cmSketch計數(shù)法如果出現(xiàn)沖突會怎么樣呢?會給同一個位置多計算訪問次數(shù)。這里cmSketch選擇了以最小的統(tǒng)計數(shù)據值作為結果。這是一個不那么精確地統(tǒng)計方法,但是可以大致的反應訪問分布的規(guī)律。

因為這個算法也就有了一個名字,叫做Count-Min Sketch。

下面我們來手撕這個算法。

//根據BloomFilter來思考一下我們需要什么
//一個bit圖,n個Hash函數(shù)
//一個BitMap的實現(xiàn)
type cmRow []byte //byte = uint8 = 0000,0000 = COUNTER 4BIT = 2 counter
//64 counter
//1 uint8 =  2counter
//32 uint8 = 64 counter
func newCmRow(numCounters int64) cmRow {
   return make(cmRow, numCounters/2)
}
func (r cmRow) get(n uint64) byte {
   return byte(r[n/2]>>((n&1)*4)) & 0x0f
}
0000,0000|0000,0000| 0000,0000 make([]byte, 3) = 6 counter
func (r cmRow) increment(n uint64) {
   //定位到第i個Counter
   i := n / 2 //r[i]
   //右移距離,偶數(shù)為0,奇數(shù)為4
   s := (n & 1) * 4
   //取前4Bit還是后4Bit
   v := (r[i] >> s) & 0x0f //0000, 1111
   //沒有超出最大計數(shù)時,計數(shù)+1
   if v < 15 {
      r[i] += 1 << s
   }
}
//cmRow 100,
//保鮮
func (r cmRow) reset() {
   // 計數(shù)減半
   for i := range r {
      r[i] = (r[i] >> 1) & 0x77 //0111,0111
   }
}
func (r cmRow) clear() {
   // 清空計數(shù)
   for i := range r {
      r[i] = 0
   }
}
//快速計算最接近x的二次冪的算法
//比如x=5,返回8
//x = 110,返回128
//2^n
//1000000 (n個0)
//01111111(n個1) + 1
// x = 1001010 = 1111111 + 1 =10000000 
func next2Power(x int64) int64 {
   x--
   x |= x >> 1 
   x |= x >> 2
   x |= x >> 4
   x |= x >> 8
   x |= x >> 16
   x |= x >> 32
   x++
   return x
}

如果我們要給n個數(shù)據計數(shù),那么每4Bit當做一個計數(shù)器Counter,我們一共需要幾個uint8來計數(shù)呢?答案是n/2

00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
00000000000000000000000000000000
//cmSketch封裝
const cmDepth = 4
type cmSketch struct {
   rows [cmDepth]cmRow
   seed [cmDepth]uint64
   mask uint64
}
//numCounter - 1 = next2Power() = 0111111(n個1)
//0000,0000|0000,0000|0000,0000
//0000,0000|0000,0000|0000,0000
//0000,0000|0000,0000|0000,0000
//0000,0000|0000,0000|0000,0000
func newCmSketch(numCounters int64) *cmSketch {
   if numCounters == 0 {
      panic("cmSketch: bad numCounters")
   }
   numCounters = next2Power(numCounters)
   sketch := &cmSketch{mask: uint64(numCounters - 1)}
   // Initialize rows of counters and seeds.
   source := rand.New(rand.NewSource(time.Now().UnixNano()))
   for i := 0; i < cmDepth; i++ {
      sketch.seed[i] = source.Uint64()
      sketch.rows[i] = newCmRow(numCounters)
   }
   return sketch
}
func (s *cmSketch) Increment(hashed uint64) {
   for i := range s.rows {
      s.rows[i].increment((hashed ^ s.seed[i]) & s.mask)
   }
}
// 找到最小的計數(shù)值
func (s *cmSketch) Estimate(hashed uint64) int64 {
   min := byte(255)
   for i := range s.rows {
      val := s.rows[i].get((hashed ^ s.seed[i]) & s.mask)
      if val < min {
         min = val
      }
   }
   return int64(min)
}
// 讓所有計數(shù)器都減半,保鮮機制
func (s *cmSketch) Reset() {
   for _, r := range s.rows {
      r.reset()
   }
}
// 清空所有計數(shù)器
func (s *cmSketch) Clear() {
   for _, r := range s.rows {
      r.clear()
   }
}

TinyLFU解決了LFU統(tǒng)計的內存消耗問題,和緩存保鮮的問題,但是TinyLFU是否還有缺點呢?

有,論文中是這么描述的,根據實測TinyLFU應對突發(fā)的稀疏流量時表現(xiàn)不佳。大概思考一下也可以得知,這些稀疏流量的訪問頻次不足以讓他們在LFU緩存中占據位置,很快就又被淘汰了。

我們回顧之前講過的,LRU對于稀疏流量效果很好,那可以不可以把LRU和LFU結合一下呢?就出現(xiàn)了下面這種緩存策略。

三、Window-TinyLFU

Window-TinyLFU策略里包含LRU和LFU兩部分,前端的小LRU叫做Window LRU,它的容量只占據1%的總空間,它的目的就是用來存放短期的突發(fā)訪問數(shù)據。存放主要元素的Segmented LRU(SLRU)是一種LRU的改進,主要把在一個時間窗口內命中至少2次的記錄和命中1次的單獨存放,這樣就可以把短期內較頻繁的緩存元素區(qū)分開來。具體做法上,SLRU包含2個固定尺寸的LRU,一個叫Probation段A1,一個叫Protection段A2。新記錄總是插入到A1中,當A1的記錄被再次訪問,就把它移到A2,當A2滿了需要驅逐記錄時,會把驅逐記錄插入到A1中。W-TinyLFU中,SLRU有80%空間被分配給A2段。

視頻講解

以上就是LRU LFU TinyLFU緩存算法實例詳解的詳細內容,更多關于LRU LFU TinyLFU緩存算法的資料請關注腳本之家其它相關文章!

相關文章

  • go?goth封裝第三方認證庫示例詳解

    go?goth封裝第三方認證庫示例詳解

    這篇文章主要為大家介紹了go?goth封裝第三方認證庫示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2022-08-08
  • 如何使用Golang創(chuàng)建與讀取Excel文件

    如何使用Golang創(chuàng)建與讀取Excel文件

    我最近工作忙于作圖,圖表,需要自己準備數(shù)據源,所以經常和Excel打交道,下面這篇文章主要給大家介紹了關于如何使用Golang創(chuàng)建與讀取Excel文件的相關資料,需要的朋友可以參考下
    2022-07-07
  • go語言中gorm時間格式化

    go語言中gorm時間格式化

    本文主要介紹了go語言中gorm時間格式化,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2023-03-03
  • 淺談go中cgo的幾種使用方式

    淺談go中cgo的幾種使用方式

    本文主要介紹了淺談go中cgo的幾種使用方式,文中根據實例編碼詳細介紹的十分詳盡,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • Golang 實現(xiàn)簡單隨機負載均衡

    Golang 實現(xiàn)簡單隨機負載均衡

    均衡算法又分為 隨機,輪詢,加權輪詢,哈希,而隨機負載均衡算法就是本文的重點,需要的朋友們下面隨著小編來一起學習學習吧
    2021-06-06
  • Go語言實現(xiàn)文件上傳

    Go語言實現(xiàn)文件上傳

    這篇文章主要為大家詳細介紹了Go語言實現(xiàn)文件上傳,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-07-07
  • Bililive-go 實現(xiàn)直播自動監(jiān)控錄制功能

    Bililive-go 實現(xiàn)直播自動監(jiān)控錄制功能

    最近有直播錄制的需求,但是自己手動錄制太麻煩繁瑣,于是用了開源項目Bililive-go進行全自動監(jiān)控錄制,對Bililive-go 直播自動監(jiān)控錄制實現(xiàn)思路感興趣的朋友,一起看看吧
    2024-03-03
  • golang實現(xiàn)簡單rpc調用過程解析

    golang實現(xiàn)簡單rpc調用過程解析

    這篇文章主要介紹了golang實現(xiàn)簡單rpc調用,包括RPC具體實現(xiàn)結合實例代碼給大家講解的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2022-05-05
  • 深入解析Golang中JSON的編碼與解碼

    深入解析Golang中JSON的編碼與解碼

    隨著互聯(lián)網的快速發(fā)展和數(shù)據交換的廣泛應用,各種數(shù)據格式的處理成為軟件開發(fā)中的關鍵問題,本文將介紹?Golang?中?JSON?編碼與解碼的相關知識,幫助大家了解其基本原理和高效應用,需要的可以收藏一下
    2023-05-05
  • go語言Timer計時器的用法示例詳解

    go語言Timer計時器的用法示例詳解

    Go語言的標準庫里提供兩種類型的計時器Timer和Ticker。這篇文章通過實例代碼給大家介紹go語言Timer計時器的用法,代碼簡單易懂,感興趣的朋友跟隨小編一起看看吧
    2020-05-05

最新評論