파이썬/Basic

lru_cache : 캐싱 메모리

코샵 2025. 2. 8. 10:09
반응형

lru_cache는 Python의 functools 모듈에서 제공하는 데코레이터로, Least Recently Used(LRU) 캐싱 메커니즘을 구현합니다. 함수의 호출 결과를 메모리에 캐싱하여 동일한 입력에 대한 반복 계산을 방지합니다.

기본 사용법

from functools import lru_cache

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

# 실행 예시
print(fibonacci(100))  # 매우 빠르게 계산됨

 

캐시 크기와 성능 측정

import time
from functools import lru_cache

def measure_time(func, *args):
    start = time.time()
    result = func(*args)
    end = time.time()
    return result, end - start

# 캐시 크기 다르게 설정하여 비교
@lru_cache(maxsize=None)  # 무제한 캐시
def factorial1(n):
    return n * factorial1(n-1) if n else 1

@lru_cache(maxsize=128)   # 제한된 캐시
def factorial2(n):
    return n * factorial2(n-1) if n else 1

# 성능 비교
for n in [100, 500, 1000]:
    result1, time1 = measure_time(factorial1, n)
    result2, time2 = measure_time(factorial2, n)
    print(f"n={n}:")
    print(f"Unlimited cache: {time1:.6f}s")
    print(f"Limited cache: {time2:.6f}s")

 

캐시 정보 확인과 관리

from functools import lru_cache

@lru_cache(maxsize=32)
def expensive_computation(x, y):
    # 복잡한 계산 시뮬레이션
    return x * y ** 2

# 캐시 통계 확인
def print_cache_info(func):
    info = func.cache_info()
    print(f"Hits: {info.hits}")        # 캐시 히트 횟수
    print(f"Misses: {info.misses}")    # 캐시 미스 횟수
    print(f"Maxsize: {info.maxsize}")  # 최대 캐시 크기
    print(f"Current size: {info.currsize}")  # 현재 캐시 크기

# 캐시 사용 예시
for i in range(5):
    expensive_computation(2, 3)
    expensive_computation(3, 4)

print_cache_info(expensive_computation)

# 캐시 초기화
expensive_computation.cache_clear()

 

실제 활용 사례

API 요청 캐싱

import requests
from functools import lru_cache
from datetime import datetime, timedelta

@lru_cache(maxsize=100)
def get_weather_data(city: str, date: str):
    """날씨 API 호출 결과를 캐싱"""
    url = f"https://api.weather.com/{city}/{date}"
    response = requests.get(url)
    return response.json()

# 사용 예시
weather = get_weather_data("seoul", "2024-02-05")

데이터베이스 쿼리 캐싱

from functools import lru_cache
from typing import Optional
import sqlite3

class DatabaseCache:
    def __init__(self):
        self.conn = sqlite3.connect('database.db')
        
    @lru_cache(maxsize=1000)
    def get_user_by_id(self, user_id: int) -> Optional[dict]:
        cursor = self.conn.cursor()
        cursor.execute("SELECT * FROM users WHERE id = ?", (user_id,))
        result = cursor.fetchone()
        return dict(zip(['id', 'name', 'email'], result)) if result else None

# 사용 예시
db = DatabaseCache()
user = db.get_user_by_id(123)  # 첫 호출: DB 쿼리 실행
user = db.get_user_by_id(123)  # 두 번째 호출: 캐시에서 반환

 

계산 비용이 높은 함수 최적화

import math
from functools import lru_cache

@lru_cache(maxsize=None)
def calculate_complex_formula(x: float, y: float, precision: int) -> float:
    """복잡한 수학적 계산을 캐싱"""
    result = 0
    for i in range(precision):
        result += math.sin(x * i) * math.cos(y * i)
    return result

# 성능 측정
def measure_performance():
    start = time.time()
    for x in range(100):
        for y in range(100):
            calculate_complex_formula(x/10, y/10, 1000)
    end = time.time()
    return end - start

 

주의사항과 최적화 팁

메모리 사용량 고려

# 메모리 사용량 모니터링
import sys

def get_size(obj):
    return sys.getsizeof(obj)

@lru_cache(maxsize=100)
def memory_intensive_function(n):
    # 큰 리스트 생성
    return [i**2 for i in range(n)]

# 메모리 사용량 체크
before = get_size(memory_intensive_function.cache_info())
memory_intensive_function(1000)
after = get_size(memory_intensive_function.cache_info())
print(f"Cache size increased by: {after - before} bytes")

 

함수 인자 제한 

from functools import lru_cache
from typing import Hashable

def make_hashable(obj):
    """객체를 해시 가능한 형태로 변환"""
    if isinstance(obj, Hashable):
        return obj
    elif isinstance(obj, dict):
        return tuple((k, make_hashable(v)) for k, v in sorted(obj.items()))
    elif isinstance(obj, (list, tuple)):
        return tuple(make_hashable(x) for x in obj)
    else:
        raise TypeError(f"Cannot make {type(obj)} hashable")

@lru_cache(maxsize=128)
def process_data(data):
    # 해시 가능한 형태로 변환 후 처리
    hashable_data = make_hashable(data)
    return str(hashable_data)

lru_cache는 적절히 사용하면 프로그램의 성능을 크게 향상시킬 수 있지만, 메모리 사용량과 캐시 크기의 균형을 잘 고려해야 합니다. 특히 대규모 애플리케이션에서는 캐시 전략을 신중하게 설계해야 합니다.