为什么需要性能优化
在 Python 开发中,性能优化是一个永恒的话题。无论是 Web 应用、数据处理管道还是机器学习模型,性能问题都可能成为系统瓶颈。本文将介绍如何使用 Python 内置的 profiling 工具来定位性能瓶颈,并提供实用的优化技巧。
一、Python Profiling 工具概览
1.1 cProfile - 内置性能分析器
cProfile 是 Python 标准库中的确定性性能分析器,可以统计每个函数的调用次数和耗时。
python
import cProfile
import pstats
from io import StringIO
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
def main():
result = fibonacci(30)
print(f"fibonacci(30) = {result}")
if __name__ == "__main__":
# 创建 Profiler
profiler = cProfile.Profile()
profiler.enable()
# 运行目标函数
main()
# 禁用 Profiler
profiler.disable()
# 打印统计结果
stats = pstats.Stats(profiler)
stats.sort_stats('cumulative') # 按累计时间排序
stats.print_stats(10) # 只显示前 10 个函数
输出示例:
vbnet
2692537 function calls in 1.234 seconds
Ordered by: cumulative time
List reduced from 2 to 10 due to restriction <10>
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.001 0.001 1.234 1.234 test.py:10(main)
2692537 1.230 0.000 1.230 0.000 test.py:5(fibonacci)
1.2 line_profiler - 行级性能分析
line_profiler 可以精确到每一行的执行时间,帮助定位具体的性能瓶颈。
ini
# 安装:pip install line_profiler
from line_profiler import LineProfiler
@profile
def slow_function():
result = 0
for i in range(100000):
result += i ** 2
return result
if __name__ == "__main__":
lp = LineProfiler()
lp_wrapper = lp(slow_function)
lp_wrapper()
lp.print_stats()
运行方式:
bash
# 使用 kernprof 运行
kernprof -l -v your_script.py
1.3 memory_profiler - 内存使用分析
python
# 安装:pip install memory_profiler
from memory_profiler import profile
@profile
def memory_intensive():
data = []
for i in range(1000000):
data.append([i] * 100)
return len(data)
if __name__ == "__main__":
memory_intensive()
二、实际案例分析
案例 1:列表推导 vs 循环
python
import time
def method_loop(n):
result = []
for i in range(n):
result.append(i ** 2)
return result
def method_comprehension(n):
return [i ** 2 for i in range(n)]
def method_map(n):
return list(map(lambda x: x ** 2, range(n)))
# 性能对比
n = 10000000
for method in [method_loop, method_comprehension, method_map]:
start = time.perf_counter()
method(n)
end = time.perf_counter()
print(f"{method.__name__}: {end - start:.3f}s")
输出示例:
yaml
method_loop: 0.892s
method_comprehension: 0.654s # 最快
method_map: 0.781s
案例 2:字符串拼接优化
python
# ❌ 低效方式
def concat_bad(n):
result = ""
for i in range(n):
result += str(i)
return result
# ✅ 高效方式
def concat_good(n):
parts = []
for i in range(n):
parts.append(str(i))
return "".join(parts)
# 使用列表推导
def concat_best(n):
return "".join(str(i) for i in range(n))
# 性能测试
n = 100000
print(f"Bad: {timeit.timeit(lambda: concat_bad(n), number=10):.3f}s")
print(f"Good: {timeit.timeit(lambda: concat_good(n), number=10):.3f}s")
print(f"Best: {timeit.timeit(lambda: concat_best(n), number=10):.3f}s")
三、常用优化技巧
3.1 使用局部变量
python
import time
def use_global():
result = 0
for i in range(1000000):
result += len(str(i)) # len 是全局函数
return result
def use_local():
_len = len # 将全局函数赋值给局部变量
result = 0
for i in range(1000000):
result += _len(str(i))
return result
# 局部变量访问更快
3.2 使用生成器节省内存
python
# ❌ 占用大量内存
def get_squares_list(n):
return [i ** 2 for i in range(n)]
# ✅ 使用生成器
def get_squares_gen(n):
return (i ** 2 for i in range(n))
# 测试内存使用
import sys
print(f"List: {sys.getsizeof(get_squares_list(10000))} bytes")
print(f"Gen: {sys.getsizeof(get_squares_gen(10000))} bytes")
3.3 使用内置函数和库
python
import math
import numpy as np
# ❌ Python 原生循环
def sum_squares_loop(n):
total = 0
for i in range(n):
total += i ** 2
return total
# ✅ 使用内置函数
def sum_squares_builtin(n):
return sum(i ** 2 for i in range(n))
# ✅ 使用 NumPy(向量化)
def sum_squares_numpy(n):
return np.sum(np.arange(n) ** 2)
# 性能对比
n = 1000000
print(f"Loop: {timeit.timeit(lambda: sum_squares_loop(n), number=10):.3f}s")
print(f"Builtin: {timeit.timeit(lambda: sum_squares_builtin(n), number=10):.3f}s")
print(f"NumPy: {timeit.timeit(lambda: sum_squares_numpy(n), number=10):.3f}s")
四、并发优化
4.1 使用 concurrent.futures
python
import time
from concurrent.futures import ThreadPoolExecutor, ProcessPoolExecutor
def heavy_computation(n):
total = 0
for i in range(n):
total += i ** 2
return total
# 串行执行
def serial():
return [heavy_computation(100000) for _ in range(10)]
# 多线程(适用于 I/O 密集型)
def with_threads():
with ThreadPoolExecutor() as executor:
return list(executor.map(heavy_computation, [100000] * 10))
# 多进程(适用于 CPU 密集型)
def with_processes():
with ProcessPoolExecutor() as executor:
return list(executor.map(heavy_computation, [100000] * 10))
# 性能对比
print(f"Serial: {timeit.timeit(serial, number=1):.3f}s")
print(f"Threads: {timeit.timeit(with_threads, number=1):.3f}s")
print(f"Processes: {timeit.timeit(with_processes, number=1):.3f}s")
五、性能测试最佳实践
5.1 使用 timeit 模块
python
import timeit
# 简单测试
result = timeit.timeit(
stmt="[i ** 2 for i in range(1000)]",
number=10000
)
print(f"Time: {result:.4f}s")
# 测试自定义函数
def my_function():
return sum(i ** 2 for i in range(1000))
result = timeit.timeit(my_function, number=10000)
print(f"Custom function: {result:.4f}s")
5.2 使用 perf_counter 进行高精度测量
css
from time import perf_counter
def benchmark(func, *args, iterations=100):
times = []
for _ in range(iterations):
start = perf_counter()
func(*args)
end = perf_counter()
times.append(end - start)
return {
'min': min(times),
'max': max(times),
'mean': sum(times) / len(times),
'std': (sum((t - sum(times)/len(times))**2 for t in times) / len(times)) ** 0.5
}
# 使用
result = benchmark(my_function)
print(f"Min: {result['min']:.6f}s")
print(f"Mean: {result['mean']:.6f}s")
print(f"Std: {result['std']:.6f}s")
六、总结
性能优化的核心原则:
- 先测量,再优化 - 不要盲目优化,先用 profiling 工具定位瓶颈
- 选择合适的数据结构 - 列表、字典、集合各有适用场景
- 利用内置函数和库 - C 实现的内置函数通常比纯 Python 快
- 考虑并发 - I/O 密集型用多线程,CPU 密集型用多进程
- 权衡可读性和性能 - 不要为了微小的性能提升牺牲代码可维护性
记住:过早优化是万恶之源。先写出正确的代码,再根据 profiling 结果进行有针对性的优化。