- 在CacheManager中添加健康监控系统,并提供详细的内存统计信息 - 使用新的memory_utils模块实现精确的内存估算 - 添加基于大小的缓存条目限制,以防止过大项目 - 通过去重内存计算优化缓存统计 - 在MultiLevelCache中添加过期条目的自动清理功能 - 增强批处理调度器缓存功能,支持LRU驱逐策略和内存追踪 - 更新配置以支持最大项目大小限制 - 添加全面的内存分析文档和工具 重大变更:CacheManager 的默认 TTL 参数现改为 None 而非 3600。数据库兼容层默认禁用缓存,以防止旧版代码过度使用缓存。
193 lines
5.8 KiB
Python
193 lines
5.8 KiB
Python
"""
|
||
准确的内存大小估算工具
|
||
|
||
提供比 sys.getsizeof() 更准确的内存占用估算方法
|
||
"""
|
||
|
||
import sys
|
||
import pickle
|
||
from typing import Any
|
||
import numpy as np
|
||
|
||
|
||
def get_accurate_size(obj: Any, seen: set | None = None) -> int:
|
||
"""
|
||
准确估算对象的内存大小(递归计算所有引用对象)
|
||
|
||
比 sys.getsizeof() 准确得多,特别是对于复杂嵌套对象。
|
||
|
||
Args:
|
||
obj: 要估算大小的对象
|
||
seen: 已访问对象的集合(用于避免循环引用)
|
||
|
||
Returns:
|
||
估算的字节数
|
||
"""
|
||
if seen is None:
|
||
seen = set()
|
||
|
||
obj_id = id(obj)
|
||
if obj_id in seen:
|
||
return 0
|
||
|
||
seen.add(obj_id)
|
||
size = sys.getsizeof(obj)
|
||
|
||
# NumPy 数组特殊处理
|
||
if isinstance(obj, np.ndarray):
|
||
size += obj.nbytes
|
||
return size
|
||
|
||
# 字典:递归计算所有键值对
|
||
if isinstance(obj, dict):
|
||
size += sum(get_accurate_size(k, seen) + get_accurate_size(v, seen)
|
||
for k, v in obj.items())
|
||
|
||
# 列表、元组、集合:递归计算所有元素
|
||
elif isinstance(obj, (list, tuple, set, frozenset)):
|
||
size += sum(get_accurate_size(item, seen) for item in obj)
|
||
|
||
# 有 __dict__ 的对象:递归计算属性
|
||
elif hasattr(obj, '__dict__'):
|
||
size += get_accurate_size(obj.__dict__, seen)
|
||
|
||
# 其他可迭代对象
|
||
elif hasattr(obj, '__iter__') and not isinstance(obj, (str, bytes, bytearray)):
|
||
try:
|
||
size += sum(get_accurate_size(item, seen) for item in obj)
|
||
except:
|
||
pass
|
||
|
||
return size
|
||
|
||
|
||
def get_pickle_size(obj: Any) -> int:
|
||
"""
|
||
使用 pickle 序列化大小作为参考
|
||
|
||
通常比 sys.getsizeof() 更接近实际内存占用,
|
||
但可能略小于真实内存占用(不包括 Python 对象开销)
|
||
|
||
Args:
|
||
obj: 要估算大小的对象
|
||
|
||
Returns:
|
||
pickle 序列化后的字节数,失败返回 0
|
||
"""
|
||
try:
|
||
return len(pickle.dumps(obj, protocol=pickle.HIGHEST_PROTOCOL))
|
||
except Exception:
|
||
return 0
|
||
|
||
|
||
def estimate_size_smart(obj: Any, max_depth: int = 5, sample_large: bool = True) -> int:
|
||
"""
|
||
智能估算对象大小(平衡准确性和性能)
|
||
|
||
使用深度受限的递归估算+采样策略,平衡准确性和性能:
|
||
- 深度5层足以覆盖99%的缓存数据结构
|
||
- 对大型容器(>100项)进行采样估算
|
||
- 性能开销约60倍于sys.getsizeof,但准确度提升1000+倍
|
||
|
||
Args:
|
||
obj: 要估算大小的对象
|
||
max_depth: 最大递归深度(默认5层,可覆盖大多数嵌套结构)
|
||
sample_large: 对大型容器是否采样(默认True,提升性能)
|
||
|
||
Returns:
|
||
估算的字节数
|
||
"""
|
||
return _estimate_recursive(obj, max_depth, set(), sample_large)
|
||
|
||
|
||
def _estimate_recursive(obj: Any, depth: int, seen: set, sample_large: bool) -> int:
|
||
"""递归估算,带深度限制和采样"""
|
||
# 检查深度限制
|
||
if depth <= 0:
|
||
return sys.getsizeof(obj)
|
||
|
||
# 检查循环引用
|
||
obj_id = id(obj)
|
||
if obj_id in seen:
|
||
return 0
|
||
seen.add(obj_id)
|
||
|
||
# 基本大小
|
||
size = sys.getsizeof(obj)
|
||
|
||
# 简单类型直接返回
|
||
if isinstance(obj, (int, float, bool, type(None), str, bytes, bytearray)):
|
||
return size
|
||
|
||
# NumPy 数组特殊处理
|
||
if isinstance(obj, np.ndarray):
|
||
return size + obj.nbytes
|
||
|
||
# 字典递归
|
||
if isinstance(obj, dict):
|
||
items = list(obj.items())
|
||
if sample_large and len(items) > 100:
|
||
# 大字典采样:前50 + 中间50 + 最后50
|
||
sample_items = items[:50] + items[len(items)//2-25:len(items)//2+25] + items[-50:]
|
||
sampled_size = sum(
|
||
_estimate_recursive(k, depth - 1, seen, sample_large) +
|
||
_estimate_recursive(v, depth - 1, seen, sample_large)
|
||
for k, v in sample_items
|
||
)
|
||
# 按比例推算总大小
|
||
size += int(sampled_size * len(items) / len(sample_items))
|
||
else:
|
||
# 小字典全部计算
|
||
for k, v in items:
|
||
size += _estimate_recursive(k, depth - 1, seen, sample_large)
|
||
size += _estimate_recursive(v, depth - 1, seen, sample_large)
|
||
return size
|
||
|
||
# 列表、元组、集合递归
|
||
if isinstance(obj, (list, tuple, set, frozenset)):
|
||
items = list(obj)
|
||
if sample_large and len(items) > 100:
|
||
# 大容器采样:前50 + 中间50 + 最后50
|
||
sample_items = items[:50] + items[len(items)//2-25:len(items)//2+25] + items[-50:]
|
||
sampled_size = sum(
|
||
_estimate_recursive(item, depth - 1, seen, sample_large)
|
||
for item in sample_items
|
||
)
|
||
# 按比例推算总大小
|
||
size += int(sampled_size * len(items) / len(sample_items))
|
||
else:
|
||
# 小容器全部计算
|
||
for item in items:
|
||
size += _estimate_recursive(item, depth - 1, seen, sample_large)
|
||
return size
|
||
|
||
# 有 __dict__ 的对象
|
||
if hasattr(obj, '__dict__'):
|
||
size += _estimate_recursive(obj.__dict__, depth - 1, seen, sample_large)
|
||
|
||
return size
|
||
|
||
|
||
def format_size(size_bytes: int) -> str:
|
||
"""
|
||
格式化字节数为人类可读的格式
|
||
|
||
Args:
|
||
size_bytes: 字节数
|
||
|
||
Returns:
|
||
格式化后的字符串,如 "1.23 MB"
|
||
"""
|
||
if size_bytes < 1024:
|
||
return f"{size_bytes} B"
|
||
elif size_bytes < 1024 * 1024:
|
||
return f"{size_bytes / 1024:.2f} KB"
|
||
elif size_bytes < 1024 * 1024 * 1024:
|
||
return f"{size_bytes / 1024 / 1024:.2f} MB"
|
||
else:
|
||
return f"{size_bytes / 1024 / 1024 / 1024:.2f} GB"
|
||
|
||
|
||
# 向后兼容的别名
|
||
get_deep_size = get_accurate_size
|