← 返回 Day 91 正文

Day 91 · 图解

Day 091 — 性能剖析 · 图解

性能剖析流程

┌─────────────────────────────────────────┐
│         性能剖析完整流程                 │
├─────────────────────────────────────────┤
│                                         │
│  1️⃣ 确定目标                           │
│     └→ 优化什么?为什么?               │
│     └→ 目标性能指标是什么?             │
│                                         │
│  2️⃣ 基准测试                           │
│     └→ 当前性能如何?                   │
│     └→ 用 timeit 建立基准               │
│                                         │
│  3️⃣ 剖析代码                           │
│     └→ cProfile → 找到热点函数         │
│     └→ line_profiler → 找到热点行      │
│                                         │
│  4️⃣ 分析结果                           │
│     └→ 哪些函数/行耗时最多?           │
│     └→ 为什么慢?                       │
│                                         │
│  5️⃣ 制定优化方案                       │
│     └→ 算法优化?数据结构?缓存?      │
│                                         │
│  6️⃣ 实施优化                           │
│     └→ 一次只改一个地方                │
│                                         │
│  7️⃣ 验证效果                           │
│     └→ 对比优化前后性能                │
│     └→ 确保没有引入 bug                │
│                                         │
└─────────────────────────────────────────┘

cProfile 输出解读

         6 function calls in 0.003 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.001    0.001    0.003    0.003 main.py:10(process_data)
        3    0.001    0.000    0.001    0.000 main.py:5(helper)
        1    0.000    0.000    0.000    0.000 {method 'append' of 'list' objects}
        1    0.000    0.000    0.000    0.000 {method 'items' of 'dict' objects}

关键列说明:
┌────────┬────────────────────────────────────┐
│ ncalls │ 函数被调用的次数                    │
├────────┼────────────────────────────────────┤
│tottime │ 函数自身耗时(不含子函数)            │
├────────┼────────────────────────────────────┤
│cumtime │ 累计耗时(含子函数)                  │
├────────┼────────────────────────────────────┤
│percall │ 每次调用平均耗时                    │
└────────┴────────────────────────────────────┘

line_profiler 输出解读

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     3                                           def process_data(data):
     4         1          0.1      0.1      1.2      result = []
     5      1000        150.2      0.2     58.3      for item in data:
     6      1000         80.5      0.1     31.2          transformed = item ** 2
     7      1000         20.1      0.0      7.8          result.append(transformed)
     8         1          8.5      8.5      3.3      return result

分析:
• 第5行: 循环占 58.3% 时间 → 主要瓶颈
• 第6行: 计算占 31.2% 时间 → 可优化
• 第7行: 追加占 7.8% 时间 → 合理

memory_profiler 输出解读

Line #    Mem usage    Increment   Line Contents
================================================
     3     38.5 MiB     38.5 MiB   @profile
     4                             def analyze_memory():
     5     38.5 MiB      0.0 MiB       small_list = [1, 2, 3]
     6     38.5 MiB      0.0 MiB       small_dict = {'a': 1}
     7     42.3 MiB      3.8 MiB       large_list = [i for i in range(100000)]
     8     46.1 MiB      3.8 MiB       large_dict = {i: i**2 for i in range(100000)}
     9     46.1 MiB      0.0 MiB       result = sum(large_list)
    10     42.3 MiB     -3.8 MiB       del large_list

分析:
• 第7行: 创建大列表增加 3.8 MiB
• 第8行: 创建大字典增加 3.8 MiB
• 第10行: 删除后内存释放

常见性能陷阱

┌─────────────────────────────────────────────┐
│           常见性能陷阱                       │
├──────────┬──────────────────────────────────┤
│ 循环中   │ ❌ for i in range(n):            │
│ 重复查找 │    if i in my_list: # O(n)!      │
│          │ ✅ my_set = set(my_list)         │
│          │    if i in my_set: # O(1)!       │
├──────────┼──────────────────────────────────┤
│ 字符串   │ ❌ result = ""                   │
│ 拼接     │    for s in strings:             │
│          │        result += s # 每次新建!    │
│          │ ✅ result = "".join(strings)     │
├──────────┼──────────────────────────────────┤
│ 重复     │ ❌ for i in range(n):            │
│ 计算     │    x = expensive_calc(i)         │
│          │ ✅ cache = {i: expensive_calc(i) │
│          │    for i in range(n)}            │
├──────────┼──────────────────────────────────┤
│ 在循环中 │ ❌ for i in range(n):            │
│ 创建对象 │    regex = re.compile(r'\d+')    │
│          │ ✅ regex = re.compile(r'\d+')    │
│          │    for i in range(n):            │
└──────────┴──────────────────────────────────┘

性能优化策略

优化策略优先级:
┌─────────────────────────────────────────┐
│  1. 算法优化 (最重要)                    │
│     O(n²) → O(n log n) → O(n)         │
├─────────────────────────────────────────┤
│  2. 数据结构选择                         │
│     list → set/dict (查找 O(n) → O(1)) │
├─────────────────────────────────────────┤
│  3. 减少遍历次数                         │
│     多次遍历 → 单次遍历                  │
├─────────────────────────────────────────┤
│  4. 缓存结果                             │
│     重复计算 → 缓存                      │
├─────────────────────────────────────────┤
│  5. 延迟加载                             │
│     提前加载 → 按需加载                  │
├─────────────────────────────────────────┤
│  6. 批量处理                             │
│     逐个处理 → 批量处理                  │
└─────────────────────────────────────────┘

timeit 使用方法

timeit 基本用法:
┌─────────────────────────────────────────┐
│ # 代码字符串计时                         │
│ timeit.timeit('sum(range(1000))',       │
│               number=10000)             │
├─────────────────────────────────────────┤
│ # 函数计时                              │
│ timeit.timeit(my_func,                  │
│               number=10000)             │
├─────────────────────────────────────────┤
│ # 对比两种方法                          │
│ t1 = timeit.timeit(                     │
│     '[x**2 for x in range(1000)]',     │
│     number=10000)                       │
│ t2 = timeit.timeit(                     │
│     'list(map(lambda x: x**2,          │
│     range(1000)))',                     │
│     number=10000)                       │
└─────────────────────────────────────────┘