iT邦幫忙

2024 iThome 鐵人賽

DAY 21
0
Python

Python 錦囊密技系列 第 21

【Python錦囊㊙️技21】快取(Cache)

  • 分享至 

  • xImage
  •  

快取(Cache)是增進系統效能非常重要的機制,重複使用的資料可以常駐在記憶體中,不必每次都從硬碟或資料庫讀取,因為記憶體的存取比硬碟I/O快上幾十倍。

Cache Decorator

Python內建模組functools有許多寶藏,例如第2篇介紹的partial、第3篇介紹的reduce、第6篇Decorator介紹的wraps,還有高階函數(Higher-order functions),可參閱【Functools module in Python】,本文將介紹其中的cache,筆者開發的許多系統中都採取此機制,提升系統效能。以下我們照慣例以實作說明使用方法。

實作

範例1. 以階層(factorial)計算為例,測試使用cache與否的效能差異,程式名稱為21\cache1.py。

  1. 在函數前面加cache decorator。
from functools import cache
import timeit
    
# 階層(factorial)計算
@cache
def factorial(n):
    return n * factorial(n-1) if n else 1
  1. 重覆執行2回合,每回合400次,timeit第1個輸入參數必須為字串,【globals=globals()】會搜尋程式內與字串同名的函數執行。
# 執行測試
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())
  1. 列印cache的統計數據。
print(factorial.cache_info())
  1. 測試:
python cache1.py
  1. 執行結果:第二次(10^-7)比第一次快一點(10^-4),不明顯,因為Python不允許遞迴超過495次。cache的統計數據看起來很怪異,解釋如下。
  • 目前cache(currsize)=401次:因為n=0~400。
  • 使用cache(hits):第2次呼叫時完全使用cache,共1次。
  • 未使用cache(misses):第1次呼叫均未使用cache,factorial共用了401次。
  • maxsize=None:未限制cache容量。
0.0005036999999999958
8.000000000091267e-07
CacheInfo(hits=1, misses=401, maxsize=None, currsize=401)

範例2. 改一個寫法,程式名稱為21\cache2.py。

  1. 階層(factorial)計算改用reduce。
@cache
def factorial(n):
    return reduce(lambda x, y: x * y, range(1, n+1))    
  1. 重覆執行2回合,每回合100,000次。

  2. 執行結果:第二次(10^-6)比第一次明顯快很多(2)。

2.3351420000000003
1.000000000139778e-06
CacheInfo(hits=1, misses=1, maxsize=None, currsize=1)

lru_cache

lru_cache可限制cache容量,預設為128筆,若超過筆數,就會把最近沒被用到的(Least Recently Used, LRU)資料踢出cache。

範例3. 改一個寫法,程式名稱為21\cache3.py。

  1. decorator改用lru_cache,使用迴圈計算階層,程式名稱為21\cache3.py。。
@lru_cache
def factorial(n):
    total=1
    while(n>0):
        total=total*n
        n=n-1
    return total    
  1. 執行結果:
  • 使用cache(hits):第2次呼叫時完全使用cache,共1次。
  • 未使用cache(misses):第1次呼叫未使用cache,共1次。
  • maxsize=128:預設cache容量。
CacheInfo(hits=1, misses=1, maxsize=128, currsize=1)
  1. 可使用【@lru_cache(maxsize=32)】改變cache容量。

注意事項

  1. cache必須是hashable,例如class就不行,list/dict都可以。
  2. Lambda function也可以,若帶參數,必須使用partial,例如:
functools.partial(
    requests.get,
    headers={'User-Agent': 'Chrome'}
)

資料庫實測

資料庫存取的效能常常是應用系統的成敗關鍵,為了能明顯辨識效能差異,筆者使用資料量較大的Northwind實測,程式在21\database_first目錄,為Django專案。

範例4. 使用資料庫查詢,實測cache效能差異。

  1. 在視圖views.py內依訂單統計訂單金額,並對視圖加註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) 
  1. 在21\database_first目錄,啟動Server。
python manage.py runserver  
  1. 瀏覽:
http://localhost:8000/
  1. 執行結果:在終端機觀察執行效率。
  • 沒有使用@cache約0.001秒
  • 使用@cache,第一次約0.002秒,反而較慢,因為要額外存入記憶體。
  • 第一次約0秒,cache發揮效能。

結語

本篇介紹Python內建cache機制,它可用於多執行緒(Thread-safe),程式結束後cache就會隨之銷毀,因此,cache通常是用於常駐的server程式,簡單的系統可使用Python內建cache機制,若需要叫完整的解決方案,我們會考慮知名的cache server為Redis,譬如分散式的環境,將cache儲存至獨立的server,提供多台應用程式server同時存取,即使是1台也可以分散負載,另外還有逾時(Timeout)控制、佇列(Queue)、嵌入(Embedding)向量的處理...等功能,下一篇我們就來研究Redis。
https://ithelp.ithome.com.tw/upload/images/20241003/20001976MJ0a72gUbd.png

本系列的程式碼會統一放在GitHub,本篇的程式放在src/21資料夾,歡迎讀者下載測試,如有錯誤或疏漏,請不吝指正。


上一篇
【Python錦囊㊙️技20】ChatGPT如何提升開發團隊的生產力(2)
下一篇
【Python錦囊㊙️技22】Redis Cache Server
系列文
Python 錦囊密技30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言