A. Python性能提升神器!lru_cache的介紹和講解
我們經常談論的緩存一詞,更多的類似於將硬碟中的數據存放到內存中以至於提高讀取速度,比如常說的redis,就經常用來做數據的緩存。 Python的緩存(lru_cache)是一種裝飾在被執行的函數上,將其執行的結果緩存起來,當下次請求的時候,如果請求該函數的傳參未變則直接返回緩存起來的結果而不再執行函數的一種緩存裝飾器。
那它和redis的區別在哪?有什麼優勢?怎麼使用? 下面為你講解
1.現在我們先不使用緩存來寫一個求兩數之和的函數,並調用執行它兩次:
執行結果
可以看到 test 被執行了兩次,現在我們加上緩存再進行執行:
執行結果
可以看到 test 函數只被執行了一次,第二次的調用直接輸出了結果,使用了緩存起來的值。
2.當我們使用遞歸求斐波拉契數列 (斐波那契數列指的是這樣一個數列:0,1,1,2,3,5,8,它從第3項開始,每一項都等於前兩項之和) 的時候,緩存對性能的提升就尤其明顯了:
不使用緩存求第40項的斐波拉契數列
執行時間
使用緩存求第40項的斐波拉契數列:
執行時間
兩個差距是非常明顯的,因為不使用緩存時,相當於要重復執行了很多的函數,而使用了 lru_cache 則把之前執行的函數結果已經緩存了起來,就不需要再次執行了。
查看lru_cache源碼會發現它可以傳遞兩個參數: maxsize 、 typed :
代表被lru_cache裝飾的方法最大可緩存的結果數量 (被裝飾方法傳參不同一樣,則結果不一樣;如果傳參一樣則為同一個結果) , 如果不指定傳參則默認值為128,表示最多緩存128個返回結果,當達到了128個時,有新的結果要保存時,則會刪除最舊的那個結果。如果maxsize傳入為None則表示可以緩存無限個結果;
默認為false,代表不區分數據類型,如果設置為True,則會區分傳參類型進行緩存,官方是這樣描述的:
但在python3.9.8版本下進行測試,typed為false時,按照官方的測試方法測試得到的還是會被當成不同的結果處理,這個時候typed為false還是為true都會區別緩存,這與官方文檔的描述存在差異:
執行結果
但如果是多參數的情況下,則會被當成一個結果:
執行結果
這個時候設置typed為true時,則會區別緩存:
執行結果
當傳參個數大於1時,才符合官方的說法,不清楚是不是官方舉例有誤
當傳遞的參數是dict、list等的可變參數時,lru_cache是不支持的,會報錯:
報錯結果
緩存 緩存位置 是否支持可變參數 是否支持分布式 是否支持過期時間設置 支持的數據結構 需單獨安裝 redis 緩存在redis管理的內存中 是 是 是 支持5種數據結構 是 lru_cache 緩存在應用進程的內存中,應用被關閉則被清空 否 否 否 字典(參數為:key,結果為:value) 否
經過上面的分析,lru_cache 功能相對於redis來說要簡單許多,但使用起來更加方便,適用於小型的單體應用。如果涉及的緩存的數據種類比較多並且想更好的管理緩存、或者需要緩存數據有過期時間(類似登錄驗證的token)等,使用redis是優於lru_cache的。
B. lru演算法是什麼
是一種緩存淘汰策略。計算機的緩存容量有限,如果緩存滿了就要刪除一些內容,給新內容騰位置。大家肯定希望刪掉哪些沒什麼用的緩存,而把有用的數據繼續留在緩存里,方便之後繼續使用。
LRU緩存淘汰演算法就是一種常用策略,也就是說認為最近使用過的數據應該是是「有用的」,很久都沒用過的數據應該是無用的,內存滿了就優先刪那些很久沒用過的數據。
LRU-K原理
LRU-K中的K代表最近使用的次數,因此LRU可以認為是LRU-1。LRU-K的主要目的是為了解決LRU演算法「緩存污染」的問題,其核心思想是將「最近使用過1次」的判斷標准擴展為「最近使用過K次」。
相比LRU,LRU-K需要多維護一個隊列,用於記錄所有緩存數據被訪問的歷史。只有當數據的訪問次數達到K次的時候,才將數據放入緩存。當需要淘汰數據時,LRU-K會淘汰第K次訪問時間距當前時間最大的數據。
C. lru演算法是什麼
LRU是Least Recently Used的縮寫,是一種常用的頁面置換演算法,選擇最近最久未使用的頁面予以淘汰。
該演算法賦予每個頁面一個訪問欄位,用來記錄一個頁面自上次被訪問以來所經歷的時間t,當須淘汰一個頁面時,選擇現有頁面中其t值最大的,即最近最少使用的頁面予以淘汰。
特點:
LRU 演算法弊端是存在偶發性、周期性的批量操會降低緩存的命中率,對緩存造成污染,下面幾個就是改進演算法。
LRU-K會記錄每條數據的訪問歷史,當達到 k 時,才將數據存放到緩存,在緩存內存回收時,緩存中越接近 k 的數據被優先刪除。
Two queues(2Q)相當於 LRU-2,區別是訪問歷史(首次訪問)數據緩存於 FIFO 隊列,二次及以上的數據存放LRU緩存,FIFO 隊列數據遵循該緩存的內存回收機制,LRU緩存數據遵循該緩存的內存回收機制。
D. 聯網緩存是什麼情況
緩存最初是指用於數據交換的緩沖區(稱為緩存)。當一個硬體要讀取數據時,它會先從緩存中尋找需要的數據,如果找到就直接執行,如果找不到就從內存中尋找。因為緩存的運行速度比內存快得多,所以緩存的作用就是幫助硬體運行得更快。
網路緩存,和普通緩存有點不同,是指把網路上的東西下載到存儲卡上,然後讀取。
或者用手機上網,看到的一切,其實都是下載到硬碟或者內存里,然後顯示或者播放。
看視頻也是一樣,需要邊下載邊播放。提前下載,叫緩沖,下載這些東西就是緩存。
E. LRU 緩存淘汰演算法
當要緩存某個數據的時候,先在鏈表中查找這個數據。如果沒有找到,則直接將數據放到鏈表的尾部;如果找到了,我們就把它移動到鏈表的尾部,然後淘汰頭部數據。
因為查找數據需要遍歷鏈表,所以單純用鏈表實現的 LRU 緩存淘汰演算法的時間復雜很高,是 O(n)。如果我們將散列表和雙向鏈表兩種數據結構組合使用,可以將這三個操作的時間復雜度都降低到 O(1)。
因為我們的散列表是通過鏈表法解決散列沖突的,所以每個結點會在兩條鏈中。一個鏈是剛剛我們提到的雙向鏈表,另一個鏈是散列表中的拉鏈。前驅和後繼指針是為了將結點串在雙向鏈表中,hnext 指針是為了將結點串在散列表的拉鏈中。
這整個過程涉及的查找操作都可以通過散列表來完成。其他的操作,比如刪除頭結點、鏈表尾部插入數據等,通過雙向鏈表都可以在 O(1) 的時間復雜度內完成。所以,這三個操作的時間復雜度都是 O(1)。
至此,我們就通過散列表和雙向鏈表的組合使用,實現了一個高效的、支持 LRU 緩存淘汰演算法的緩存系統原型。
散列表中數據是經過散列函數打亂之後無規律存儲的,但是 LinkedHashMap可以 做到按照數據的插入順序來存儲。
每次調用 put() 函數,往 LinkedHashMap 中添加數據的時候,都會將數據添加到鏈表的尾部,再次將鍵值為 3 的數據放入到 LinkedHashMap 的時候,會先查找這個鍵值是否已經有了,然後,再將已經存在的 (3,11) 刪除,並且將新的 (3,26) 放到鏈表的尾部,訪問到 key 為 5 的數據的時候,我們將被訪問到的數據移動到鏈表的尾部。
所以按照訪問時間排序的 LinkedHashMap 本身就是一個支持 LRU 緩存淘汰策略的緩存系統。
散列表這種數據結構雖然支持非常高效的數據插入、刪除、查找操作,但是散列表中的數據都是通過散列函數打亂之後無規律存儲的。也就說,它無法支持按照某種順序快速地遍歷數據。如果希望按照順序遍歷散列表中的數據,那我們需要將散列表中的數據拷貝到數組中,然後排序,再遍歷。
因為散列表是動態數據結構,不停地有數據的插入、刪除,所以每當我們希望按順序遍歷散列表中的數據的時候,都需要先排序,那效率勢必會很低。為了解決這個問題,我們將散列表和鏈表(或者跳錶)結合在一起使用。