快取旁路模式是一種常見的快取策略,旨在提升系統讀取效能。其實作原理是先檢查快取是否存在所需資料,若存在則直接傳回;若不存在,則從資料函式庫讀取資料,並將其存入快取,以供下次快速存取。本文以名言資料函式庫為例,使用 Redis 作為快取系統,SQLite 作為資料函式庫,並以 Python 程式碼示範如何實作快取旁路模式。程式碼中包含了資料函式庫初始化、名言新增、快取讀取等功能,並透過使用者互動介面進行操作。此外,文章也介紹了記憶化模式,一種透過快取函式運算結果來避免重複計算的技術。以費波那契數列計算為例,展示了記憶化模式如何顯著提升程式效能,並提供 Python 程式碼實作範例,說明如何使用 @lru_cache 裝飾器實作記憶化。最後,文章也簡述了延遲載入模式,一種僅在需要時才載入資源的技術,並說明其應用場景和優點。

快取旁路模式的實作與應用

快取旁路(Cache-Aside)模式是一種常見的快取策略,主要用於提升系統的讀取效能。在本篇文章中,我們將探討快取旁路模式的基本原理,並透過一個實際的範例來展示其實作方法。

快取旁路模式的基本原理

快取旁路模式的核心思想是,當應用程式需要讀取資料時,首先檢查快取中是否存在該資料。如果存在,則直接從快取中讀取;如果不存在,則從資料函式庫中讀取,並將資料存入快取中,以便下次快速讀取。

案例1:讀取資料專案

  1. 檢查快取中是否存在該資料。
  2. 如果存在,則直接從快取中讀取。
  3. 如果不存在,則從資料函式庫中讀取,並將資料存入快取中。

案例2:更新資料專案

  1. 將資料寫入資料函式庫。
  2. 從快取中刪除對應的資料專案。

實作範例:名言資料函式庫與快取系統

在這個範例中,我們將建立一個名言資料函式庫,並使用 Redis 作為快取系統。我們將實作一個簡單的應用程式,用於新增名言到資料函式庫和快取中,以及根據名言 ID 讀取名言。

環境設定與依賴套件

  • SQLite 資料函式庫:用於儲存名言資料。
  • Redis 伺服器與 redis-py Python 模組:用於實作快取系統。
  • Faker 模組:用於產生虛擬的名言資料。

資料函式庫設定與名言新增功能

import sqlite3
from pathlib import Path
from random import randint
import redis
from faker import Faker

fake = Faker()
DB_PATH = Path(__file__).parent / Path("quotes.sqlite3")
cache = redis.StrictRedis(host="localhost", port=6379, decode_responses=True)

def setup_db():
    try:
        with sqlite3.connect(DB_PATH) as db:
            cursor = db.cursor()
            cursor.execute("""
                CREATE TABLE quotes(id INTEGER PRIMARY KEY, text TEXT)
            """)
            db.commit()
            print("Table 'quotes' created")
    except Exception as e:
        print(e)

def add_quotes(quotes_list):
    added = []
    try:
        with sqlite3.connect(DB_PATH) as db:
            cursor = db.cursor()
            for quote_text in quotes_list:
                quote_id = randint(1, 100) 
                quote = (quote_id, quote_text)
                cursor.execute("""
                    INSERT OR IGNORE INTO quotes(id, text) VALUES(?, ?)
                """, quote)
                added.append(quote)
            db.commit()
    except Exception as e:
        print(e)
    return added

快取旁路模式的實作

import sqlite3
from pathlib import Path
import redis

CACHE_KEY_PREFIX = "quote"
DB_PATH = Path(__file__).parent / Path("quotes.sqlite3")
cache = redis.StrictRedis(host="localhost", port=6379, decode_responses=True)

def get_quote(quote_id: str) -> str:
    out = []
    quote = cache.get(f"{CACHE_KEY_PREFIX}.{quote_id}")
    if quote is None:
        query_fmt = "SELECT text FROM quotes WHERE id = {}"
        try:
            with sqlite3.connect(DB_PATH) as db:
                cursor = db.cursor()
                res = cursor.execute(query_fmt.format(quote_id)).fetchone()
                if not res:
                    return "There was no quote stored matching that id!"
                quote = res[0]
                out.append(f"Got '{quote}' FROM DB")
        except Exception as e:
            print(e)
            quote = ""
        if quote:
            key = f"{CACHE_KEY_PREFIX}.{quote_id}"
            cache.set(key, quote, ex=60)
            out.append(f"Added TO CACHE, with key '{key}'")
    else:
        out.append(f"Got '{quote}' FROM CACHE")
    if out:
        return " - ".join(out)
    else:
        return ""

程式碼解析

  1. get_quote函式:根據名言 ID 讀取名言。如果快取中不存在,則從資料函式庫中讀取,並將結果存入快取。
  2. 快取引擎:使用 Redis 作為快取系統,儲存名言資料。
  3. 資料函式庫操作:使用 SQLite 資料函式庫儲存名言資料,並在需要時查詢。

使用者互動介面

def main():
    while True:
        quote_id = input("Enter the ID of the quote: ")
        # ... (省略其他程式碼)

圖表翻譯:

此圖示展示了快取旁路模式的基本流程:

  graph LR
    A[開始] --> B{檢查快取}
    B -->|存在| C[從快取讀取]
    B -->|不存在| D[從資料函式庫讀取]
    D --> E[將資料存入快取]
    C --> F[傳回結果]
    E --> F

圖表翻譯: 此圖示呈現了快取旁路模式的運作流程,首先檢查快取是否存在所需資料,若存在則直接從快取讀取,若不存在則從資料函式庫讀取並存入快取,最後傳回結果。整個流程有效地減少了對資料函式庫的直接查詢,提升了系統的整體效能。

快取策略模式(Cache-Aside Pattern)實作與解析

快取策略模式是一種常見的效能最佳化技術,廣泛應用於軟體開發領域。本章節將探討快取策略模式的實作細節,並分析其在實際應用中的運作方式。

程式碼實作與測試

以下為測試快取策略模式的 Python 程式碼範例:

if quote_id.isdigit():
    out = get_quote(quote_id)
    print(out)
else:
    print("You must enter a number. Please retry.")

內容解密:

  1. 檢查 quote_id 是否為數字。
  2. 若為數字,則呼叫 get_quote(quote_id) 函式取得對應的。
  3. 若非數字,則輸出錯誤訊息,要求重新輸入。

快取策略模式的運作流程

  1. 首先,執行 python ch08/cache_aside/populate_db.py 初始化資料函式庫。
  2. 選擇 “init” 模式,建立 quotes.sqlite3 資料函式庫檔案及 quotes 表格。
  3. 執行 python ch08/cache_aside/populate_db.py 並選擇 “update_all” 模式,更新資料函式庫並將新資料加入快取。

輸出範例:

New (fake) quotes added to the database:
Added to DB: (62, 'Instead not here public.')
- Also adding to the cache
Added to DB: (26, 'Training degree crime serious beyond management and.')
- Also adding to the cache

內容解密:

  1. populate_db.py 程式碼負責初始化及更新資料函式庫內容。
  2. 當選擇 “update_all” 模式時,新的會被加入資料函式庫並同步更新至快取中。

快取策略模式的測試結果

執行 python ch08/cache_aside/cache_aside.py 並輸入不同的 ID,觀察其輸出結果:

Enter the ID of the quote: 23
Got 'Dark team exactly really wind.' FROM DB - Added TO CACHE, with key 'quote.23'
Enter the ID of the quote: 12
There was no quote stored matching that id!

內容解密:

  1. 當輸入存在的 ID(如 23),系統會從資料函式庫中取出資料並加入快取。
  2. 當輸入不存在的 ID(如 12),系統會回報查無資料。

Memoization 模式簡介

Memoization 是一種重要的效能最佳化技術,透過快取函式的計算結果,避免重複運算。

實際應用範例

  1. 費氏數列計算:透過儲存已計算的值,避免重複運算。
  2. 文字搜尋演算法:快取搜尋結果,加速相同查詢的處理速度。

Memoization 模式的實作

以下為使用 Python 的 functools.lru_cache 裝飾器實作 Memoization 的範例:

from datetime import timedelta
from functools import lru_cache

def fibonacci_func1(n):
    if n < 2:
        return n
    return fibonacci_func1(n-1) + fibonacci_func1(n-2)

@lru_cache(maxsize=None)
def fibonacci_func2(n):
    if n < 2:
        return n
    return fibonacci_func2(n-1) + fibonacci_func2(n-2)

內容解密:

  1. fibonacci_func1 為未使用快取的遞迴實作,效能較差。
  2. fibonacci_func2 使用 @lru_cache 裝飾器進行 Memoization,最佳化遞迴運算效能。
  3. maxsize=None 表示無限制快取大小。

Memoization 模式的適用場景

  1. 最佳化遞迴演算法:降低時間複雜度。
  2. 減少運算負擔:避免不必要的重複計算。
  3. 提升應用效能:改善使用者經驗。

本章節介紹了快取策略模式及 Memoization 模式的實作與應用,讀者可根據實際需求選擇適合的最佳化技術。

效能模式實務應用:記憶化與延遲載入

在軟體開發領域中,效能最佳化一直是開發者關注的重點。其中,記憶化(Memoization)與延遲載入(Lazy Loading)是兩種常見且有效的效能最佳化模式。本文將探討這兩種模式的原理、實務應用及其程式碼實作。

記憶化模式詳解

記憶化是一種透過快取函式呼叫結果來避免重複計算的技術。以下以計算費波那契數列為例,展示記憶化的強大效能提升。

傳統遞迴實作 vs. 記憶化實作

首先,我們來看傳統的費波那契數列遞迴實作:

def fibonacci_func1(n):
    if n < 2:
        return n
    return fibonacci_func1(n - 1) + fibonacci_func1(n - 2)

這種實作方式存在嚴重的效能問題,因為它會進行大量重複計算。

接下來,我們使用@lru_cache裝飾器來實作記憶化版本:

from functools import lru_cache

@lru_cache(maxsize=None)
def fibonacci_func2(n):
    if n < 2:
        return n
    return fibonacci_func2(n - 1) + fibonacci_func2(n - 2)

效能比較測試

我們編寫一個main()函式來測試這兩個函式的效能:

import time
from datetime import timedelta

def main():
    n = 30
    
    start_time = time.time()
    result = fibonacci_func1(n)
    duration = timedelta(seconds=time.time() - start_time)
    print(f"Fibonacci_func1({n}) = {result}, calculated in {duration}")
    
    start_time = time.time()
    result = fibonacci_func2(n)
    duration = timedelta(seconds=time.time() - start_time)
    print(f"Fibonacci_func2({n}) = {result}, calculated in {duration}")

if __name__ == "__main__":
    main()

執行結果顯示,fibonacci_func2(使用記憶化)的計算時間遠遠少於fibonacci_func1(傳統遞迴)。

程式碼解析

  1. 記憶化實作關鍵:使用@lru_cache(maxsize=None)裝飾器對fibonacci_func2進行記憶化處理。
  2. 效能提升原因:避免了重複計算,將已計算的結果儲存在快取中,直接傳回快取結果而非重新計算。
  3. 適用場景:任何存在重複計算的場景,如動態規劃問題、遞迴演算法最佳化等。

延遲載入模式詳解

延遲載入是一種最佳化資源載入時間的技術,僅在需要時才載入資源。

實務應用場景

  1. 網頁圖片延遲載入:僅載入可視區域內的圖片。
  2. 影片串流服務:分段載入影片內容。
  3. 電子試算表軟體:僅載入目前檢視或操作相關的資料。

程式碼實作範例

以下是一個簡單的延遲載入實作範例:

class LazyLoadedData:
    def __init__(self):
        self._data = None
    
    @property
    def data(self):
        if self._data is None:
            self._data = self.load_data()
        return self._data
    
    def load_data(self):
        print("Loading expensive data...")
        # 模擬昂貴的操作,例如從資料函式庫讀取或複雜計算
        return sum(i * i for i in range(100000))

def main():
    obj = LazyLoadedData()
    print("Object created, expensive attribute not loaded yet.")
    print("Accessing expensive attribute:")
    print(obj.data)
    print("Accessing expensive attribute again:")
    print(obj.data)

if __name__ == "__main__":
    main()

程式碼解析

  1. LazyLoadedData類別設計:透過@property裝飾器將data方法轉換為屬性,並在內部實作延遲載入邏輯。
  2. load_data方法:模擬一個耗時的資料載入或計算過程。
  3. 首次存取觸發載入:當第一次存取obj.data時,才會呼叫load_data方法進行資料載入。
  4. 再次存取直接傳回:第二次及之後的存取直接傳回已載入的資料,無需重複載入。