做者:Cameron MacLeodhtml
翻譯:老齊前端
最近,我讀了一篇有趣的文章,文中介紹了一些未充分使用的Python特性的。在文章中,做者提到,從Python 3.2開始,標準庫附帶了一個內置的裝飾器functools.lru_cache
。我發現這個裝飾器很使人興奮,有了它,咱們有可能輕鬆地爲許多應用程序加速。python
你可能在想,這很好,但這個裝飾器到底是什麼?它提供對已構建的緩存的訪問,該緩存使用LRU(譯者注: Least Recently Used的縮寫,即最近最少使用,是一種經常使用的頁面置換算法,選擇最近最久未使用的頁面予以淘汰。)的置換策略,所以被命名爲lru_cache
。固然,這句話聽起來可能有點使人膽怯,因此讓咱們把它分解一下。web
緩存是一個能夠快速訪問的地方,能夠在它裏面存儲訪問速度較慢的內容。爲了演示這一點,讓咱們以你的web瀏覽器爲例。算法
從網絡上讀取網頁可能須要幾秒鐘,即便是快速的網絡鏈接也如此。在計算機時代,這個問題是永恆的。爲了解決這個問題,瀏覽器將你已經訪問過的網頁存儲在計算機的緩存中,這樣訪問速度會加快數千倍。數據庫
使用緩存下載網頁的步驟以下:json
雖然緩存並不會讓你第一次訪問網頁的速度加快,但一般你是要多次訪問某一個網站頁面的(想一想Facebook——注:對多數國人來說,可能不是這個網站,或者你的電子郵件),有了緩存以後,之後每次訪問都會更快。flask
瀏覽器並非惟一使用緩存的,從服務器到CPU和硬盤或SSD之間的計算機硬件,它們無處不在。從緩存中能夠很快地獲取數據,所以當你不止一次獲取數據時,它能夠大大加快程序的速度。瀏覽器
緩存只能存儲有限數量的東西,並且一般它比可能存入所緩存的東西要小得多(例如,你的硬盤比互聯網小得多)。這意味着有時須要將緩存中已有內容替換掉,放入其餘內容。對於去掉什麼的決策方法被稱爲置換策略。緩存
這就是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()
將刪除緩存中的全部元素。
一般,只有在如下狀況下才能使用緩存:
原文連接:www.cameronmacleod.com/blog/python…
關注微信公衆號:老齊教室。讀深度文章,得精湛技藝,享絢麗人生。