Python脚本如何搭配内存与文件缓存

wen python案例 30

本文目录导读:

Python脚本如何搭配内存与文件缓存

  1. 核心思路
  2. 内存缓存实现
  3. 文件缓存实现
  4. 组合缓存策略(内存+文件)
  5. 高级方案与注意事项
  6. 选择路线图

Python脚本搭配内存与文件缓存是一个常见的性能优化手段,尤其适合计算密集、I/O频繁、或重复读取相同数据的场景。

以下我会从原理、策略、代码实现三个层面进行阐述,并给出可直接运行的Python示例。


核心思路

缓存类型 存储位置 速度 生命周期 适用场景
内存缓存 进程堆内存(dict、lru_cache) 纳秒级 进程存活期间 重复计算函数结果、频繁访问字典/对象
文件缓存 磁盘(pickle、json、sqlite) 毫秒级 可持久化、跨进程 大模型向量、API响应、预处理数据

最佳实践

  1. 优先用内存缓存(LRU)减少函数调用。
  2. 若数据太大或需跨会话,落盘为文件缓存。
  3. 组合:内存中放热数据,文件里放冷数据。

内存缓存实现

1 functools.lru_cache —— 自动LRU淘汰

from functools import lru_cache
@lru_cache(maxsize=128)  # 最多缓存128个结果
def expensive_function(n):
    """斐波那契、矩阵运算、数据库查询"""
    print(f"计算 {n}")
    return n ** 2
# 第一次调用会计算
print(expensive_function(10))  # 输出 "计算 10" + 100
# 第二次直接命中缓存
print(expensive_function(10))  # 仅输出 100(无"计算10")

适用:纯函数(无副作用)、递归、重复参数调用。

2 自定义字典缓存 —— 可控失效

class MemoryCache:
    def __init__(self, ttl=60):
        self.cache = {}
        self.ttl = ttl  # 过期时间,秒
    def get(self, key):
        if key in self.cache:
            value, timestamp = self.cache[key]
            if time.time() - timestamp < self.ttl:
                return value
            else:
                del self.cache[key]
        return None
    def set(self, key, value):
        self.cache[key] = (value, time.time())

适用:需控制缓存时效、手动管理。


文件缓存实现

1 使用 pickle 缓存大型对象

import pickle
import os
def load_with_cache(filename, compute_func, force=False):
    """如果文件存在且未过期,则从文件加载;否则调用 compute_func 并缓存"""
    if not force and os.path.exists(filename):
        with open(filename, 'rb') as f:
            return pickle.load(f)
    else:
        result = compute_func()
        with open(filename, 'wb') as f:
            pickle.dump(result, f)
        return result

示例

# 假设 slow_calculation 是大模型推理、网络请求、数据清洗
data = load_with_cache("cache.pkl", lambda: slow_calculation(), force=False)

注意:pickle 不安全,不要加载不可信来源;大对象建议用 joblib 替代(更高效处理Numpy数组)。

2 使用 joblib.Memory —— 智能缓存(推荐)

joblib 专为科学计算设计,自动根据输入参数和代码变化判断是否重算。

from joblib import Memory
# 指定缓存目录
mem = Memory("./cache_dir", verbose=0)
@mem.cache
def compute_expensive(x, y):
    print(f"真实计算: x={x}, y={y}")
    import time; time.sleep(2)
    return x ** y
# 首次调用会执行真实计算
result1 = compute_expensive(2, 10)
# 第二次立即返回(缓存命中)
result2 = compute_expensive(2, 10)

优势

  • 自动识别函数参数。
  • 当函数源代码改变时,自动重新计算(哈希校验)。
  • 支持大数组分块序列化(numpy友好)。

组合缓存策略(内存+文件)

典型的双层缓存(L1内存 + L2文件):

import json
import os
import pickle
from functools import lru_cache
class HybridCache:
    def __init__(self, file_path="cache.pkl", memory_maxsize=128):
        self.file_path = file_path
        self.mem_cache = lru_cache(maxsize=memory_maxsize)(self._load_or_compute)
    def _load_or_compute(self, key):
        """先查文件,若文件无则计算并存文件"""
        # 尝试从文件加载
        if os.path.exists(self.file_path):
            with open(self.file_path, 'rb') as f:
                data = pickle.load(f)
                if key in data:
                    return data[key]
        # 没有缓存 -> 计算
        value = self._expensive_compute(key)
        # 写回文件(简化:每次全量写入)
        if os.path.exists(self.file_path):
            with open(self.file_path, 'rb') as f:
                data = pickle.load(f)
        else:
            data = {}
        data[key] = value
        with open(self.file_path, 'wb') as f:
            pickle.dump(data, f)
        return value
    def _expensive_compute(self, key):
        """模拟耗时计算"""
        print(f"真实计算 key={key}")
        return key ** 2
    def get(self, key):
        return self.mem_cache(key)

使用方式

hc = HybridCache("big_cache.pkl", memory_maxsize=100)
print(hc.get(5))   # 第一次:内存未命中->文件未命中->计算->存文件->存内存
print(hc.get(5))   # 第二次:内存命中(极快)
print(hc.get(10))  # 第三次:内存未命中->文件命中(如果已缓存过10)

高级方案与注意事项

1 针对大数据的文件缓存:diskcache

diskcache 是一个专为磁盘缓存优化的库,支持:

  • 分段存储:每个key一个文件,避免全量读写。
  • 过期时间:TTL自动清理。
  • 大容量:支持GB级缓存。
pip install diskcache
from diskcache import Cache
cache = Cache("my_cache_dir")
@cache.memoize(expire=3600)  # 缓存1小时
def expensive_function(a, b):
    return a ** b
print(expensive_function(2, 10))

2 缓存失效策略

  • 时间过期:设置TTL(如expire)。
  • 显式清除:删除文件、cache.clear()
  • 版本号:函数代码变更时自动失效(joblib支持)。
  • LRU/LFU:内存用functools.lru_cache,文件用diskcache的LRU策略。

3 并发安全

  • 内存缓存:仅在单进程内工作,多进程需加锁或使用共享内存(multiprocessing.Manager)。
  • 文件缓存:避免多进程同时写同一文件,推荐:
    • 使用SQLite作为文件缓存(支持并发读、WAL模式写)。
    • 或使用filelock库加锁。

选择路线图

graph TD
    A[需要缓存吗?] -->|否| B[直接计算]
    A -->|是| C{数据量大小?}
    C -->|小于数十MB| D[内存缓存: lru_cache 或 dict]
    C -->|数十MB到几GB| E[文件缓存: pickle/joblib/diskcache]
    C -->|更大| F[数据库 / Redis / 分布式缓存]
    D --> G{需要跨进程/持久化?}
    G -->|是| E
    G -->|否| D
    E --> H{需要自动过期/版本管理?}
    H -->|简单| I[pickle + 手动]
    H -->|高级| J[joblib.Memory 或 diskcache]

实践建议

  • 简单场景直接用 joblib.Memory(自动管理哈希和IO)。
  • 需要细粒度TTL控制用 diskcache
  • 仅内存缓存就用 lru_cache
  • 避免自己造轮子(序列化、并发、过期策略容易出bug)。

这样,你的Python脚本既能享受内存的极速访问,又能利用文件系统实现数据持久化和跨会话复用。

抱歉,评论功能暂时关闭!