Skip to content

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 = x
python
# ❌ 反模式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 缓存函数结果                                  │
│                                                             │
└─────────────────────────────────────────────────────────────┘