本文是《Python工程化实践》专栏第十四章,讲解性能分析工具和常见优化技巧,让你的代码跑得更快。


1. 性能优化的原则

1.1 不要过早优化

“过早优化是万恶之源” —— Donald Knuth

在优化之前,先问自己:

  • 这个代码真的慢吗?
  • 慢在哪里?
  • 优化后收益有多大?

1.2 先 profiling 再优化

没有数据的优化是瞎子摸象。

# 先运行,找到慢的地方
python -m cProfile -s cumtime your_script.py

1.3 找到瓶颈再动手

90% 的时间花在 10% 的代码上

优化热点代码才能事半功倍。


2. cProfile 性能分析

2.1 基本用法

python -m cProfile -s cumtime your_script.py

输出示例:

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
   1000000    0.215    0.000    0.215    0.000 profile_demo.py:3(<genexpr>)
        100    0.102    0.001    1.523    0.015 profile_demo.py:7(main)
        100    0.089    0.001    1.421    0.014 profile_demo.py:3(slow_function)

列说明:

列说明
ncalls调用次数
tottime函数自身执行时间(不含子调用)
percalltottime / ncalls
cumtime累计时间(含子调用)
percallcumtime / ncalls

2.2 解读报告

关注 cumtime 大的函数:

ncalls  cumtime  function
100     5.234    slow_function  ← 热点!

2.3 使用 pstats 分析

import cProfile
import pstats
from io import StringIO

profiler = cProfile.Profile()
profiler.enable()

# 运行你的代码
main()

profiler.disable()

# 分析
stats = pstats.Stats(profiler)
stats.strip_dirs()  # 去除路径前缀
stats.sort_stats("cumulative")  # 按累计时间排序
stats.print_stats(20)  # 只显示前 20 行

2.4 常用排序

stats.sort_stats("cumulative")   # 累计时间
stats.sort_stats("tottime")       # 纯函数时间
stats.sort_stats("filename")      # 按文件名
stats.sort_stats("name")         # 按函数名

3. line_profiler

3.1 安装

pip install line_profiler

3.2 逐行分析

# profile_line.py
def slow_function():
    total = 0
    for i in range(1000):
        total += i ** 2  # 平方运算
    return total

def main():
    for _ in range(100):
        slow_function()

if __name__ == "__main__":
    from line_profiler import LineProfiler

    profiler = LineProfiler()
    profiler.add_function(slow_function)
    profiler.enable_by_count()

    main()

    profiler.print_stats()

3.3 命令行使用

kernprof -l -v profile_line.py

输出:

Timer unit: 1e-06 s

File: profile_line.py
Function: slow_function at line 2

Line #  Hits         Time  Per Hit   % Time  Line Contents
==============================================================================
   2         1      100.0     100.0     0.1  def slow_function():
   3      101      500.0       5.0     0.5  total = 0
   4    101000   50000.0       0.5    50.0  for i in range(1000):
   5    100000   45000.0       0.5    45.0      total += i ** 2
   6         1      100.0     100.0     0.1  return total

4. 常见性能陷阱

4.1 字符串拼接

# ❌ 慢:每次创建新字符串
result = ""
for i in range(10000):
    result += str(i)

# ✅ 快:列表 join
parts = []
for i in range(10000):
    parts.append(str(i))
result = "".join(parts)

原因:字符串是不可变对象,+= 每次都创建新字符串并复制。

4.2 循环中的重复计算

# ❌ 慢:每次循环都调用 len()
data = list(range(10000))
for i in range(len(data)):  # len() 每次调用
    pass

# ✅ 快:缓存长度
n = len(data)
for i in range(n):  # 只调用一次
    pass

4.3 使用局部变量

# ❌ 慢:全局变量查找
import math
for i in range(100000):
    math.sin(i)

# ✅ 快:局部变量缓存
import math
sin = math.sin
for i in range(100000):
    sin(i)

4.4 列表 vs 生成器

# ❌ 内存占用高:一次性创建整个列表
result = [i * 2 for i in range(1000000)]

# ✅ 内存友好:生成器
result = (i * 2 for i in range(1000000))

# ✅ 内存友好:列表推导式但延迟计算
result = [i * 2 for i in range(1000000)]  # 如果后面有 join 等操作,列表推导式反而更快

5. 数据结构选择

5.1 list vs set 查找

# ❌ 慢:列表线性查找 O(n)
def find_in_list(items, target):
    for item in items:
        if item == target:
            return True
    return False

# ✅ 快:集合查找 O(1)
def find_in_set(items, target):
    return target in set(items)

# 如果要多次查找,先转 set
items_set = set(items)  # 一次转换
for target in targets:
    if target in items_set:  # 每次 O(1)
        pass

5.2 deque 队列

from collections import deque

# deque 头部操作 O(1),列表头部操作 O(n)
dq = deque(maxlen=1000)

for i in range(2000):
    dq.append(i)

# deque 自动淘汰旧数据
# dq: [1000, 1001, ..., 1999]  始终保持 1000 个

5.3 dict vs list 迭代

# 多次迭代时,dict 比 list 更快查找
d = {i: i for i in range(10000)}

# 单次迭代不需要转 set
for key in d:
    pass  # 直接遍历 dict

6. 函数调用开销

6.1 方法绑定

import time

class MyClass:
    def do_something(self):
        pass

obj = MyClass()
do = obj.do_something  # 绑定方法

# 测试
start = time.time()
for _ in range(100000):
    obj.do_something()
print(f"方法调用: {time.time() - start:.4f}s")

start = time.time()
for _ in range(100000):
    do()  # 绑定后稍快
print(f"绑定调用: {time.time() - start:.4f}s")

6.2 局部变量缓存

import math
import time

def slow():
    for _ in range(100000):
        math.sin(1)

def fast():
    sin = math.sin  # 缓存到局部变量
    for _ in range(100000):
        sin(1)

7. 缓存装饰器

7.1 functools.lru_cache

from functools import lru_cache
import time

@lru_cache(maxsize=128)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

# 测量
start = time.time()
result = fibonacci(100)
print(f"Time: {time.time() - start:.4f}s")

7.2 缓存策略

@lru_cache(maxsize=1024)  # 缓存 1024 个结果
def expensive_func(arg):
    return compute(arg)

# 缓存参数必须是可哈希的(hashable)
# 列表、字典等不可作为缓存参数

7.3 缓存清除

@lru_cache(maxsize=128)
def expensive_func(x):
    return x * x

# 清除所有缓存
expensive_func.cache_clear()

# 查看缓存信息
print(expensive_func.cache_info())
# CacheInfo(hits=5, misses=10, maxsize=128, currsize=5)

8. Cython 入门

8.1 什么是 Cython

Cython 是 Python 的超集,可以将 Python 代码编译成 C 代码,获得显著加速。

8.2 安装

pip install cython

8.3 基础用法

# mymath.pyx(Cython 文件)
def slow_sum(int n):
    cdef int i
    cdef int total = 0
    for i in range(n):
        total += i
    return total
# setup.py
from setuptools import setup
from Cython.Build import cythonize

setup(
    name="mymath",
    ext_modules=cythonize("mymath.pyx"),
)
# 编译
python setup.py build_ext --inplace

8.4 类型声明加速

# 普通 Python
def python_sum(n):
    total = 0
    for i in range(n):
        total += i
    return total

# Cython 优化(声明类型)
def cython_sum(int n):
    cdef int i
    cdef int total = 0
    for i in range(n):
        total += i
    return total

9. 总结

这一章我们讨论了性能优化的核心要素:

  • profiling:先 cProfile 找到瓶颈,不要瞎优化
  • 常见陷阱:字符串拼接、重复计算、全局变量
  • 数据结构:set 查找、deque 队列
  • 缓存:lru_cache 避免重复计算
  • Cython:类型声明获得 C 语言级加速

性能优化黄金法则:

  1. 先 profiling
  2. 找到热点
  3. 针对性优化
  4. 再次 profiling 验证效果

不要为了"看起来更聪明"而优化,要为了"真的变快了"而优化。

下一章预告:下一章我们将介绍 Git Hooks 与预提交——让代码在提交前就完成检查,把好质量的最后一道关。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐