01-性能优化
本章讲解 Python 性能分析和代码优化的技巧。
性能分析
cProfile
python
import cProfile
import pstats
def slow_function():
total = 0
for i in range(1000000):
total += i * i
return total
# 代码中分析
profiler = cProfile.Profile()
profiler.enable()
slow_function()
profiler.disable()
# 输出统计
stats = pstats.Stats(profiler)
stats.sort_stats('cumulative')
stats.print_stats(10)py-spy
bash
# 安装
pip install py-spy
# 附加到进程
py-spy top --pid 12345
# 生成火焰图
py-spy record -o profile.svg -- python script.py代码优化
使用内置函数
python
# ❌ 慢
result = []
for i in range(1000):
result.append(i * 2)
# ✅ 快:列表推导式
result = [i * 2 for i in range(1000)]
# ✅ 快:内置 sum
total = sum(range(1000))字符串拼接
python
# ❌ 慢:O(n²)
result = ""
for i in range(10000):
result += str(i)
# ✅ 快:O(n)
result = "".join(str(i) for i in range(10000))生成器
python
# ❌ 占用大量内存
def get_squares(n):
result = []
for i in range(n):
result.append(i * i)
return result
# ✅ 节省内存
def get_squares_gen(n):
for i in range(n):
yield i * i集合查找
python
# ❌ 慢:O(n)
items = [1, 2, 3, 4, 5]
if 3 in items:
pass
# ✅ 快:O(1)
items_set = {1, 2, 3, 4, 5}
if 3 in items_set:
pass数据结构选择
python
from collections import deque, defaultdict, Counter
# 两端操作:deque
queue = deque()
queue.append(1)
queue.appendleft(0)
# 默认值:defaultdict
d = defaultdict(list)
d['key'].append(1)
# 计数:Counter
counter = Counter(['a', 'b', 'a', 'c'])
print(counter.most_common(1)) # [('a', 2)]slots 内存优化
概念铺垫
┌─────────────────────────────────────────────────────────────┐
│ __slots__ 基本概念 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. __slots__ 是什么? │
│ ───────────────────────────────────────────── │
│ • 类属性,声明实例允许的属性列表 │
│ • 替代 __dict__,用固定数组存储属性 │
│ • 节省内存,提高属性访问速度 │
│ │
│ 2. 普通 class vs __slots__ class │
│ ───────────────────────────────────────────── │
│ 普通 class: │
│ • 每个实例有 __dict__ 字典 │
│ • 属性存储在字典中 │
│ • 可以动态添加新属性 │
│ • 内存开销大 │
│ │
│ __slots__ class: │
│ • 无 __dict__ │
│ • 属性存储在固定大小的数组 │
│ • 不能动态添加新属性 │
│ • 内存开销小 │
│ │
│ 3. 内存节省原理 │
│ ───────────────────────────────────────────── │
│ __dict__ 开销: │
│ • 字典对象本身:约 72 bytes │
│ • 每个键值对:约 72 bytes │
│ • 动态扩容预留空间 │
│ │
│ __slots__ 开销: │
│ • 无字典对象 │
│ • 每个属性:指针大小(8 bytes) │
│ • 固定大小,无预留 │
│ │
│ 4. 与 dataclass 的关系 │
│ ───────────────────────────────────────────── │
│ @dataclass(slots=True) │
│ • Python 3.10+ 支持 │
│ • 自动生成 __slots__ │
│ • 无需手动定义 │
│ │
└─────────────────────────────────────────────────────────────┘最简示例
python
# 普通 class(有 __dict__)
class Person:
def __init__(self, name: str, age: int) -> None:
self.name = name
self.age = age
# __slots__ class(无 __dict__)
class PersonSlots:
__slots__ = ['name', 'age']
def __init__(self, name: str, age: int) -> None:
self.name = name
self.age = age
# 使用
p1 = Person("张三", 25)
p2 = PersonSlots("张三", 25)
print(p1.name) # 张三
print(p2.name) # 张三(用法相同)内存对比验证
python
import sys
class Person:
def __init__(self, name: str, age: int) -> None:
self.name = name
self.age = age
class PersonSlots:
__slots__ = ['name', 'age']
def __init__(self, name: str, age: int) -> None:
self.name = name
self.age = age
# 单个实例
p1 = Person("张三", 25)
p2 = PersonSlots("张三", 25)
print(f"普通 class: {sys.getsizeof(p1)} bytes") # ~56 bytes(不含 __dict__)
print(f"__slots__ class: {sys.getsizeof(p2)} bytes") # ~48 bytes
# 加上 __dict__ 开销
print(f"普通 class + __dict__: {sys.getsizeof(p1) + sys.getsizeof(p1.__dict__)} bytes")
# ~56 + 72 = ~128 bytes
# 大量实例对比
persons = [Person(str(i), i) for i in range(100000)]
person_slots = [PersonSlots(str(i), i) for i in range(100000)]
print(f"100000 普通 class: {sum(sys.getsizeof(p) + sys.getsizeof(p.__dict__) for p in persons) / 1024 / 1024:.1f} MB")
# ~12 MB
print(f"100000 __slots__ class: {sum(sys.getsizeof(p) for p in person_slots) / 1024 / 1024:.1f} MB")
# ~4.6 MB(节省 ~60%)slots 的限制
┌─────────────────────────────────────────────────────────────┐
│ __slots__ 使用限制 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 1. 不能动态添加属性 │
│ ───────────────────────────────────────────── │
│ class Point: │
│ __slots__ = ['x', 'y'] │
│ │
│ p = Point() │
│ p.x = 1.0 # OK │
│ p.z = 3.0 # AttributeError! │
│ │
│ 2. 不能使用类变量作为默认值 │
│ ───────────────────────────────────────────── │
│ class Wrong: │
│ __slots__ = ['x'] │
│ x = 0 # ❌ 类变量和 __slots__ 冲突 │
│ │
│ # ✅ 正确:在 __init__ 中设置默认值 │
│ class Right: │
│ __slots__ = ['x'] │
│ def __init__(self): │
│ self.x = 0 │
│ │
│ 3. 继承时父类也需要 __slots__ │
│ ───────────────────────────────────────────── │
│ class Base: │
│ pass # 无 __slots__,有 __dict__ │
│ │
│ class Derived(Base): │
│ __slots__ = ['x'] │
│ │
│ d = Derived() │
│ # 父类有 __dict__,所以 d 还可以动态添加属性 │
│ d.y = 1 # OK(继承的 __dict__) │
│ │
│ # ✅ 正确:父类也用 __slots__ │
│ class BaseSlots: │
│ __slots__ = [] │
│ │
│ class DerivedSlots(BaseSlots): │
│ __slots__ = ['x', 'y'] │
│ │
│ d = DerivedSlots() │
│ # d.y = 1 # AttributeError(完全无 __dict__) │
│ │
│ 4. 影响 pickle、weakref 等特性 │
│ ───────────────────────────────────────────── │
│ • pickle 需要显式添加 '__dict__' 到 __slots__ │
│ • weakref 需要显式添加 '__weakref__' │
│ │
│ class Serializable: │
│ __slots__ = ['x', '__dict__', '__weakref__'] │
│ │
└─────────────────────────────────────────────────────────────┘继承时的 slots
python
# ❌ 父类无 __slots__:子类仍有 __dict__
class Base:
pass
class Derived(Base):
__slots__ = ['x']
d = Derived()
d.x = 1 # OK(__slots__)
d.y = 2 # OK(继承的 __dict__)
print(d.__dict__) # {'y': 2}
# ✅ 父类也用 __slots__:完全无 __dict__
class BaseSlots:
__slots__ = []
class DerivedSlots(BaseSlots):
__slots__ = ['x', 'y']
d = DerivedSlots()
d.x = 1 # OK
d.y = 2 # OK
# d.z = 3 # AttributeError(完全无 __dict__)
print(hasattr(d, '__dict__')) # False与 dataclass(slots=True) 对比
python
from dataclasses import dataclass
import sys
# 手动 __slots__
class PointManual:
__slots__ = ['x', 'y']
def __init__(self, x: float, y: float) -> None:
self.x = x
self.y = y
def __repr__(self) -> str:
return f"Point({self.x}, {self.y})"
# dataclass(slots=True)(Python 3.10+)
@dataclass(slots=True)
class PointDataclass:
x: float
y: float
# 内存相同
p1 = PointManual(1.0, 2.0)
p2 = PointDataclass(1.0, 2.0)
print(sys.getsizeof(p1)) # ~48
print(sys.getsizeof(p2)) # ~48
# dataclass(slots=True) 自动生成:
# • __slots__ = ('x', 'y')
# • __init__、__repr__、__eq__
# • 无需手动写适用场景
| 场景 | 是否推荐 slots | 原因 |
|---|---|---|
| 大量实例(>10000) | ✅ 推荐 | 内存节省显著 |
| 属性固定的数据类 | ✅ 推荐 | 无需动态属性 |
| 游戏对象、粒子系统 | ✅ 推荐 | 内存敏感 |
| 配置类、设置类 | ✅ 推荐 | 属性固定 |
| 需要动态添加属性 | ❌ 不推荐 | slots 禁止动态 |
| 需要 pickle 序列化 | ⚠️ 需额外处理 | 添加 'dict' |
| 单例或少量实例 | ❌ 不推荐 | 收益不明显 |
反模式
python
# ❌ 反模式1:__slots__ 与类变量冲突
class Wrong:
__slots__ = ['x']
x = 0 # 冲突!
# ✅ 正确:默认值在 __init__ 中
class Right:
__slots__ = ['x']
def __init__(self, x: float = 0.0) -> None:
self.x = xpython
# ❌ 反模式2:只为少量实例用 __slots__
class Config:
__slots__ = ['host', 'port']
config = Config("localhost", 8080) # 只有1个实例,节省不明显
# ✅ 正确:大量实例才值得
class Particle:
__slots__ = ['x', 'y', 'vx', 'vy']
particles = [Particle() for _ in range(100000)] # 大量实例python
# ❌ 反模式3:父类无 __slots__
class Base:
pass # 有 __dict__
class Derived(Base):
__slots__ = ['x'] # 仍有 __dict__
# ✅ 正确:父类也用 __slots__
class Base:
__slots__ = []
class Derived(Base):
__slots__ = ['x']缓存
lru_cache
python
from functools import lru_cache
@lru_cache(maxsize=None)
def fibonacci(n):
if n < 2:
return n
return fibonacci(n-1) + fibonacci(n-2)优化原则
| 原则 | 说明 |
|---|---|
| 先分析后优化 | 不要猜测瓶颈,用工具测量 |
| 80/20 法则 | 20% 的代码占用 80% 的时间 |
| 优先优化热点 | 优化最频繁执行的代码 |
| 保持可读性 | 不要过度优化 |
本章小结
┌─────────────────────────────────────────────────────────────┐
│ 性能优化 知识要点 │
├─────────────────────────────────────────────────────────────┤
│ │
│ 性能分析: │
│ ✓ cProfile 分析函数耗时 │
│ ✓ py-spy 生成火焰图 │
│ │
│ 代码优化: │
│ ✓ 使用内置函数和列表推导式 │
│ ✓ 使用生成器节省内存 │
│ ✓ 使用集合加速查找 │
│ │
│ __slots__ 内存优化: │
│ ✓ 替代 __dict__,固定属性列表 │
│ ✓ 大量实例节省 ~60% 内存 │
│ ✓ 不能动态添加属性 │
│ ✓ 父类也需 __slots__ 才完全生效 │
│ ✓ @dataclass(slots=True) 自动生成 │
│ │
│ 缓存: │
│ ✓ lru_cache 缓存函数结果 │
│ │
└─────────────────────────────────────────────────────────────┘