快取(Cache)是增進系統效能非常重要的機制,重複使用的資料可以常駐在記憶體中,不必每次都從硬碟或資料庫讀取,因為記憶體的存取比硬碟I/O快上幾十倍。
Python內建模組functools有許多寶藏,例如第2篇介紹的partial、第3篇介紹的reduce、第6篇Decorator介紹的wraps,還有高階函數(Higher-order functions),可參閱【Functools module in Python】,本文將介紹其中的cache,筆者開發的許多系統中都採取此機制,提升系統效能。以下我們照慣例以實作說明使用方法。
範例1. 以階層(factorial)計算為例,測試使用cache與否的效能差異,程式名稱為21\cache1.py。
from functools import cache
import timeit
# 階層(factorial)計算
@cache
def factorial(n):
return n * factorial(n-1) if n else 1
# 執行測試
n = 400
print(
timeit.timeit(
"factorial(n)",
globals=globals(),
number=1
)
)
# run again
print(
timeit.timeit(
"factorial(n)",
globals=globals(),
number=1
)
)
print(factorial.cache_info())
print(factorial.cache_info())
python cache1.py
0.0005036999999999958
8.000000000091267e-07
CacheInfo(hits=1, misses=401, maxsize=None, currsize=401)
範例2. 改一個寫法,程式名稱為21\cache2.py。
@cache
def factorial(n):
return reduce(lambda x, y: x * y, range(1, n+1))
重覆執行2回合,每回合100,000次。
執行結果:第二次(10^-6)比第一次明顯快很多(2)。
2.3351420000000003
1.000000000139778e-06
CacheInfo(hits=1, misses=1, maxsize=None, currsize=1)
lru_cache可限制cache容量,預設為128筆,若超過筆數,就會把最近沒被用到的(Least Recently Used, LRU)資料踢出cache。
範例3. 改一個寫法,程式名稱為21\cache3.py。
@lru_cache
def factorial(n):
total=1
while(n>0):
total=total*n
n=n-1
return total
CacheInfo(hits=1, misses=1, maxsize=128, currsize=1)
functools.partial(
requests.get,
headers={'User-Agent': 'Chrome'}
)
資料庫存取的效能常常是應用系統的成敗關鍵,為了能明顯辨識效能差異,筆者使用資料量較大的Northwind實測,程式在21\database_first目錄,為Django專案。
範例4. 使用資料庫查詢,實測cache效能差異。
@cache
def index(request):
start_time = time.time()
summary = OrderDetails.objects.values('orderid') \
.annotate(total=Sum(ExpressionWrapper(F('unitprice') * F('quantity')
, output_field=DecimalField())))
context = {"summary":summary}
print("--- %s seconds ---" % (time.time() - start_time))
return render(request, "index.html", context)
python manage.py runserver
http://localhost:8000/
本篇介紹Python內建cache機制,它可用於多執行緒(Thread-safe),程式結束後cache就會隨之銷毀,因此,cache通常是用於常駐的server程式,簡單的系統可使用Python內建cache機制,若需要叫完整的解決方案,我們會考慮知名的cache server為Redis,譬如分散式的環境,將cache儲存至獨立的server,提供多台應用程式server同時存取,即使是1台也可以分散負載,另外還有逾時(Timeout)控制、佇列(Queue)、嵌入(Embedding)向量的處理...等功能,下一篇我們就來研究Redis。
本系列的程式碼會統一放在GitHub,本篇的程式放在src/21資料夾,歡迎讀者下載測試,如有錯誤或疏漏,請不吝指正。