Python 性能优化:让代码快 10 倍的秘诀
·
本文是《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 | 函数自身执行时间(不含子调用) |
| percall | tottime / ncalls |
| cumtime | 累计时间(含子调用) |
| percall | cumtime / 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 语言级加速
性能优化黄金法则:
- 先 profiling
- 找到热点
- 针对性优化
- 再次 profiling 验证效果
不要为了"看起来更聪明"而优化,要为了"真的变快了"而优化。
下一章预告:下一章我们将介绍 Git Hooks 与预提交——让代码在提交前就完成检查,把好质量的最后一道关。
更多推荐


所有评论(0)