最近,我讀了一篇有趣的文章,文中介紹了一些未充分使用的Python特性的。在文章中,作者提到,從Python 3.2開始,標準庫附帶了一個內置的裝飾器functools.lru_cache。我發現這個裝飾器很令人興奮,有了它,我們有可能輕鬆地為許多應用程式加速。 你可能在想,這很好,但這個裝飾器究竟 ...
最近,我讀了一篇有趣的文章,文中介紹了一些未充分使用的Python特性的。在文章中,作者提到,從Python 3.2開始,標準庫附帶了一個內置的裝飾器functools.lru_cache
。我發現這個裝飾器很令人興奮,有了它,我們有可能輕鬆地為許多應用程式加速。
你可能在想,這很好,但這個裝飾器究竟是什麼?它提供對已構建的緩存的訪問,該緩存使用LRU(譯者註: Least Recently Used的縮寫,即最近最少使用,是一種常用的頁面置換演算法,選擇最近最久未使用的頁面予以淘汰。)的置換策略,因此被命名為lru_cache
。當然,這句話聽起來可能有點令人膽怯,所以讓我們把它分解一下。
這裡也要註意:不管你是為了Python就業還是興趣愛好,記住:項目開發經驗永遠是核心,如果你沒有2020最新python入門到高級實戰視頻教程,可以去小編的Python交流.裙 :七衣衣九七七巴而五(數字的諧音)轉換下可以找到了,裡面很多新python教程項目,還可以跟老司機交流討教!
什麼是緩存?
緩存是一個可以快速訪問的地方,可以在它裡面存儲訪問速度較慢的內容。為了演示這一點,讓我們以你的web瀏覽器為例。
從網路上讀取網頁可能需要幾秒鐘,即使是快速的網路連接也如此。在電腦時代,這個問題是永恆的。為瞭解決這個問題,瀏覽器將你已經訪問過的網頁存儲在電腦的緩存中,這樣訪問速度會加快數千倍。
使用緩存下載網頁的步驟如下:
- 檢查頁面的本地緩存。如果頁面在那裡,返回該頁面。
- 在因網上找到網頁並從那裡下載。
- 將該網頁存儲在緩存中,以便將來更快地訪問。
雖然緩存並不會讓你第一次訪問網頁的速度加快,但通常你是要屢次訪問某一個網站頁面的(想想Facebook——註:對多數國人來講,可能不是這個網站,或者你的電子郵件),有了緩存之後,以後每次訪問都會更快。
瀏覽器並不是唯一使用緩存的,從伺服器到CPU和硬碟或SSD之間的電腦硬體,它們無處不在。從緩存中可以很快地獲取數據,因此當你不止一次獲取數據時,它可以大大加快程式的速度。
LRU是什麼意思?
緩存只能存儲有限數量的東西,而且通常它比可能存入所緩存的東西要小得多(例如,你的硬碟比互聯網小得多)。這意味著有時需要將緩存中已有內容替換掉,放入其他內容。對於去掉什麼的決策方法被稱為置換策略。
這就是LRU的用武之地。LRU代表最近用得最少的緩存中內容,這是一種常用的緩存置換策略。
為什麼置換策略很重要?
“最近使用最少”這種置換策略的基本思想是:如果你有一段時間沒有訪問過某個東西,你可能近期不會訪問它。要使用此策略,只需在緩存已滿時刪除最早使用的項即可。
在上圖中,緩存中的每個項都附帶了訪問時間。依據LRU策略,選擇訪問時間為2:55PM 的項作為要置換的項,因為它是最早被訪問的。如果有兩個對象具有相同的訪問時間,那麼LRU將從中隨機選擇一個。
這種去掉長時間不用的東西的策略,被稱為Bélády的最優演算法,它是置換緩存內容的最佳策略。當然,我們根本不知道未來會有什麼操作。謝天謝地,在許多情況下,LRU提供了近乎最佳的性能。
怎樣使用它?
functools.lru_cache
是一個裝飾器,因此你可以將它放在函數的頂部:
import functools
@functools.lru_cache(maxsize=128)
def fib(n):
if n < 2:
return 1
return fib(n-1) + fib(n-2)
複製代碼
Fibonacci數列在遞歸示例中經常被用到,要提升這個函數的速度,使用functools.lru_cache
之後,不費吹灰之力,就能讓這個遞歸函數狂飆。在我的機器上運行這些代碼,得到了這個函數有緩存版本和沒有緩存版本的以下結果。
$ python3 -m timeit -s 'from fib_test import fib' 'fib(30)'
10 loops, best of 3: 282 msec per loop
$ python3 -m timeit -s 'from fib_test import fib_cache' 'fib_cache(30)'
10000000 loops, best of 3: 0.0791 usec per loop
複製代碼
增加一行代碼之後,速度提高了3565107倍。
當然,我認為很難看出你在實際中會如何使用它,因為我們很少需要計算斐波那契數列。回到web頁面示例,我們可以舉一個更實際的用緩存渲染前端模板的例子。
在伺服器開發中,通常單個頁面存儲為具有占位符變數的模板。例如,下麵是一個頁面模板,該頁面顯示某一天各種足球比賽的結果。
<html>
<body>
<h1>Matches for {{day}}</h1>
<table id="matches">
<tr>
<td>Home team</td>
<td>Away team</td>
<td>Score</td>
</tr>
{% for match in matches %}
<tr>
<td>{{match["home"]}}</td>
<td>{{match["away"]}}</td>
<td>{{match["home_goals"]}} - {{match["away_goals"]}}</td>
</tr>
{% endfor %}
</table>
</body>
</html>
複製代碼
呈現模板時,看起來如下所示:
這是緩存的主要目標,因為每天的結果不會改變,而且很可能每天會有多次訪問。下麵是一個提供此模板的Flask應用程式。我引入了50ms的延遲來模擬通過網路或者從大型資料庫獲取匹配字典。
import json
import time
from flask import Flask, render_template
app = Flask(__name__)
with open('match.json','r') as f:
match_dict = json.load(f)
def get_matches(day):
# simulate network/database delay
time.sleep(0.05)
return match_dict[day]
@app.route('/matches/<day>')
def show_matches(day):
matches = get_matches(day)
return render_template('matches.html', matches=matches, day=day)
if __name__ == "__main__":
app.run()
複製代碼
使用requests
在不緩存的情況下獲得三天的數據,在我的電腦上本地運行平均需要171ms。這還不錯,但我們可以做得更好,即使考慮到人為的延遲。
@app.route('/matches/<day>')
@functools.lru_cache(maxsize=4)
def show_matches(day):
matches = get_matches(day)
return render_template('matches.html', matches=matches, day=day)
複製代碼
在本例中,我設置了maxsize=4
,因為我的測試腳本只有相同的三天,最好設置2次冪。使用這種方法,10個迴圈的平均速度可以降到13.7ms。
還有什麼應該知道?
Python文檔雖然很詳細,但是有一些東西還是要強調的。
內置函數
裝飾器附帶了一些很有用的內置函數。cache_info()
返回訪問數(hits)、未訪問數(misses)和當前緩存使用量(currsize)、最大容量(maxsize),幫助你瞭解緩存使用情況。cache_clear()
將刪除緩存中的所有元素。
有時候不要使用緩存
通常,只有在以下情況下才能使用緩存:
- 在緩存期內,數據不會更改。
- 函數將始終為相同的參數返回相同的值(因此時間和隨機對緩存沒有意義)。
- 函數沒有副作用。如果緩存被訪問,則永遠不會調用該函數,因此請確保不更改其中的任何狀態。
- 函數不返回不同的可變對象。例如,返回列表的函數不適合緩存,因為將要緩存的是對列表的引用,而不是列表內容。
最後註意:不管你是為了Python就業還是興趣愛好,記住:項目開發經驗永遠是核心,如果你沒有2020最新python入門到高級實戰視頻教程,可以去小編的Python交流.裙 :七衣衣九七七巴而五(數字的諧音)轉換下可以找到了,裡面很多新python教程項目,還可以跟老司機交流討教!本文的文字及圖片來源於網路加上自己的想法,僅供學習、交流使用,不具有任何商業用途,版權歸原作者所有,如有問題請及時聯繫我們以作處理。