本文目录导读:

Python脚本搭配内存与文件缓存是一个常见的性能优化手段,尤其适合计算密集、I/O频繁、或重复读取相同数据的场景。
以下我会从原理、策略、代码实现三个层面进行阐述,并给出可直接运行的Python示例。
核心思路
| 缓存类型 | 存储位置 | 速度 | 生命周期 | 适用场景 |
|---|---|---|---|---|
| 内存缓存 | 进程堆内存(dict、lru_cache) | 纳秒级 | 进程存活期间 | 重复计算函数结果、频繁访问字典/对象 |
| 文件缓存 | 磁盘(pickle、json、sqlite) | 毫秒级 | 可持久化、跨进程 | 大模型向量、API响应、预处理数据 |
最佳实践:
- 优先用内存缓存(LRU)减少函数调用。
- 若数据太大或需跨会话,落盘为文件缓存。
- 组合:内存中放热数据,文件里放冷数据。
内存缓存实现
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脚本既能享受内存的极速访问,又能利用文件系统实现数据持久化和跨会话复用。