mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
165 字
1 分钟
Python 内存管理与性能优化
2024-02-14

一、内存管理机制#

1.1 引用计数#

import sys
# 每个对象维护一个引用计数
a = [] # 引用计数 +1
b = a # 引用计数 +1
print(sys.getrefcount(a)) # 3 (包括 getrefcount 的临时引用)
del a # 引用计数 -1
del b # 引用计数 -1 -> 对象被回收
# 引用计数为 0 时,对象立即被销毁

1.2 循环引用问题#

# 循环引用导致引用计数无法归零
class Node:
def __init__(self):
self.next = None
a = Node()
b = Node()
a.next = b # a -> b
b.next = a # b -> a
# 引用计数:a=1, b=1(相互引用)
# 无法通过引用计数回收,造成内存泄漏
# 解决:标记-清除(Mark-Sweep)算法
import gc
gc.collect() # 手动触发垃圾回收

1.3 分代回收#

import gc
# Python 将对象分为三代
# 第 0 代:新创建的对象
# 第 1 代:经历一次 GC 仍存活的对象
# 第 2 代:经历两次 GC 仍存活的对象
# 分代阈值
print(gc.get_threshold()) # (700, 10, 10)
# 触发条件:第 0 代对象数量 > 700
# 分代回收策略
# 第 0 代:频繁回收
# 第 1 代:次频繁回收
# 第 2 代:很少回收
# 自动回收
gc.enable() # 启用自动回收
gc.disable() # 禁用自动回收

二、内存泄漏与排查#

2.1 常见内存泄漏#

# 1. 全局变量引用
leaked_data = []
def add_data(item):
leaked_data.append(item) # 数据永不释放
# 2. 闭包引用
def create_leak():
data = [1, 2, 3]
def inner():
return data # data 被闭包持有
return inner
# 3. 循环引用
class Node:
def __init__(self):
self.self_ref = self # 循环引用
# 4. 监听器未移除
class EventEmitter:
def __init__(self):
self.listeners = []
def on(self, event, callback):
self.listeners.append((event, callback))
# 如果没有 off() 移除,监听器会累积
# 5. 缓存未清理
cache = {}
def get_data(key):
if key not in cache:
cache[key] = load_data(key) # 缓存无限增长
return cache[key]

2.2 tracemalloc 排查#

import tracemalloc
# 启动跟踪
tracemalloc.start()
# ... 执行代码 ...
# 获取快照
snapshot = tracemalloc.take_snapshot()
# 显示最大内存占用
top_stats = snapshot.statistics('lineno')
for stat in top_stats[:10]:
print(stat)
# 按文件名过滤
stats = snapshot.filter_traces((
tracemalloc.Filter(False, "<module>"),
))

2.3 objgraph 排查#

# pip install objgraph
import objgraph
# 显示增长最多的对象类型
print(objgraph.show_most_common_types(limit=10))
# 显示某个类型的所有实例
print(objgraph.by_type('MyClass'))
# 统计某个对象的引用
obj = some_object()
print(objgraph.find_refchain(obj, max_depth=5))

三、slots 优化#

3.1 内存优化#

import sys
class WithoutSlots:
def __init__(self, x, y):
self.x = x
self.y = y
class WithSlots:
__slots__ = ['x', 'y']
def __init__(self, x, y):
self.x = x
self.y = y
# 对比内存占用
obj1 = WithoutSlots(1, 2)
obj2 = WithSlots(1, 2)
print(sys.getsizeof(obj1)) # 56 bytes (含 __dict__)
print(sys.getsizeof(obj2)) # 40 bytes (更小)
# 注意:slots 不影响实例方法的内存

3.2 防止动态属性#

class SafeClass:
__slots__ = ['x', 'y']
def __init__(self, x, y):
self.x = x
self.y = y
obj = SafeClass(1, 2)
obj.z = 3 # AttributeError: 'SafeClass' object has no attribute 'z'

四、性能优化技巧#

4.1 避免全局变量查找#

# 慢:每次访问 math 模块都需查找
def slow_sin():
import math
for i in range(1000):
math.sin(i)
# 快:模块级导入
import math
def fast_sin():
for i in range(1000):
math.sin(i)

4.2 列表操作优化#

# 慢:append in loop
result = []
for i in range(10000):
result.append(i * 2)
# 快:列表推导式
result = [i * 2 for i in range(10000)]
# 更快:生成器(内存)
result = (i * 2 for i in range(10000))
# 字符串拼接优化
# 慢
s = ""
for i in range(1000):
s += str(i)
# 快
parts = []
for i in range(1000):
parts.append(str(i))
s = "".join(parts)
# 最快(如果可能)
s = "".join(str(i) for i in range(1000))

4.3 map/filter 替代循环#

# map 比循环快
import time
start = time.perf_counter()
result = list(map(lambda x: x * 2, range(1000000)))
print(f"map: {time.perf_counter() - start:.4f}s")
start = time.perf_counter()
result = [x * 2 for x in range(1000000)]
print(f"list comp: {time.perf_counter() - start:.4f}s")

4.4 使用局部变量#

# 慢:全局变量查找开销
def slow_func():
for i in range(1000):
value = math.sin(i) + math.cos(i)
return value
# 快:缓存局部变量
def fast_func():
sin = math.sin
cos = math.cos
for i in range(1000):
value = sin(i) + cos(i)
return value

4.5 itertools 优化#

from itertools import islice
# 迭代器替代列表切片
# 慢
data = list(range(1000000))
subset = data[100:200]
# 快:使用 islice
subset = list(islice(range(1000000), 100, 200))

五、缓存与记忆化#

5.1 lru_cache#

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.cache_info())
# CacheInfo(hits=0, misses=0, maxsize=128, currsize=0)
fibonacci(10)
print(fibonacci.cache_info())
# CacheInfo(hits=8, misses=11, maxsize=128, currsize=11)
# 清除缓存
fibonacci.cache_clear()

5.2 自定义缓存#

from functools import lru_cache
import time
# 带过期时间的缓存
class Cache:
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
del self.cache[key]
return None
def set(self, key, value):
self.cache[key] = (value, time.time())
def clear(self):
self.cache.clear()

六、常用标准库性能对比#

操作低效写法高效写法
列表创建list(range(n))[i for i in range(n)]
字符串拼接s += str(i)''.join([str(i)])
成员判断i in listi in set
唯一元素list(set(items))dict.fromkeys(items).keys()
统计计数items.count(x)collections.Counter(items)

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

Python 内存管理与性能优化
https://blog.souloss.com/posts/interview/python-memory-and-performance/
作者
Souloss
发布于
2024-02-14
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时