文章目录
-
- 性能优化的三个层次
- 层次一:诊断工具
-
- cProfile:找到最耗时的函数
- line_profiler:逐行分析
- tracemalloc:内存泄漏与峰值定位
- 持续监控内存峰值
- 层次二:算法优化
-
- 使用正确的数据结构
- 使用 `collections.deque` 替代 `list.pop(0)`
- 使用 `functools.lru_cache` 缓存重复计算
- 使用生成器替代列表
- 循环优化:将不变计算提到循环外
- 层次三:底层加速
-
- numba:JIT 编译提速
- Cython:Python 的超集
- 使用 `__slots__` 减少内存
- 性能优化决策树
- 性能优化的工程原则
-
- 1. 测量 → 优化 → 测量
- 2. 优化收益递减规律
- 3. 不要过早优化
- 综合实战:日志分析器性能优化
- 工具箱速查
- 优化优先级的黄金法则
- 系列结语
“Python 太慢了”——这句话对了一半。纯 Python 的 CPU 密集型计算确实慢,但性能优化从来不是从重写 C 开始,而是从找到瓶颈开始。
性能优化的三个层次
#mermaid-svg-ikVTearY2mLbmKfJ{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ikVTearY2mLbmKfJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ikVTearY2mLbmKfJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ikVTearY2mLbmKfJ .error-icon{fill:#552222;}#mermaid-svg-ikVTearY2mLbmKfJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ikVTearY2mLbmKfJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ikVTearY2mLbmKfJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ikVTearY2mLbmKfJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ikVTearY2mLbmKfJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ikVTearY2mLbmKfJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ikVTearY2mLbmKfJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ikVTearY2mLbmKfJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ikVTearY2mLbmKfJ .marker.cross{stroke:#333333;}#mermaid-svg-ikVTearY2mLbmKfJ svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ikVTearY2mLbmKfJ p{margin:0;}#mermaid-svg-ikVTearY2mLbmKfJ .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-ikVTearY2mLbmKfJ .cluster-label text{fill:#333;}#mermaid-svg-ikVTearY2mLbmKfJ .cluster-label span{color:#333;}#mermaid-svg-ikVTearY2mLbmKfJ .cluster-label span p{background-color:transparent;}#mermaid-svg-ikVTearY2mLbmKfJ .label text,#mermaid-svg-ikVTearY2mLbmKfJ span{fill:#333;color:#333;}#mermaid-svg-ikVTearY2mLbmKfJ .node rect,#mermaid-svg-ikVTearY2mLbmKfJ .node circle,#mermaid-svg-ikVTearY2mLbmKfJ .node ellipse,#mermaid-svg-ikVTearY2mLbmKfJ .node polygon,#mermaid-svg-ikVTearY2mLbmKfJ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ikVTearY2mLbmKfJ .rough-node .label text,#mermaid-svg-ikVTearY2mLbmKfJ .node .label text,#mermaid-svg-ikVTearY2mLbmKfJ .image-shape .label,#mermaid-svg-ikVTearY2mLbmKfJ .icon-shape .label{text-anchor:middle;}#mermaid-svg-ikVTearY2mLbmKfJ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ikVTearY2mLbmKfJ .rough-node .label,#mermaid-svg-ikVTearY2mLbmKfJ .node .label,#mermaid-svg-ikVTearY2mLbmKfJ .image-shape .label,#mermaid-svg-ikVTearY2mLbmKfJ .icon-shape .label{text-align:center;}#mermaid-svg-ikVTearY2mLbmKfJ .node.clickable{cursor:pointer;}#mermaid-svg-ikVTearY2mLbmKfJ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ikVTearY2mLbmKfJ .arrowheadPath{fill:#333333;}#mermaid-svg-ikVTearY2mLbmKfJ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ikVTearY2mLbmKfJ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ikVTearY2mLbmKfJ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ikVTearY2mLbmKfJ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ikVTearY2mLbmKfJ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ikVTearY2mLbmKfJ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ikVTearY2mLbmKfJ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ikVTearY2mLbmKfJ .cluster text{fill:#333;}#mermaid-svg-ikVTearY2mLbmKfJ .cluster span{color:#333;}#mermaid-svg-ikVTearY2mLbmKfJ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-ikVTearY2mLbmKfJ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ikVTearY2mLbmKfJ rect.text{fill:none;stroke-width:0;}#mermaid-svg-ikVTearY2mLbmKfJ .icon-shape,#mermaid-svg-ikVTearY2mLbmKfJ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ikVTearY2mLbmKfJ .icon-shape p,#mermaid-svg-ikVTearY2mLbmKfJ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ikVTearY2mLbmKfJ .icon-shape .label rect,#mermaid-svg-ikVTearY2mLbmKfJ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ikVTearY2mLbmKfJ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ikVTearY2mLbmKfJ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ikVTearY2mLbmKfJ :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
是
否
性能问题
层次一:诊断cProfile + tracemalloc找到瓶颈在哪
层次二:算法优化数据结构 / 缓存 / 惰性求值不用改语言,效果 10x+
优化后是否满足需求?
完成 ✅
层次三:底层加速Cython / Numba / C 扩展热路径用编译代码替换
核心原则:没有测量就没有优化。盲目优化是浪费时间——80% 的执行时间通常集中在 20% 的代码中。先找到那 20%。
层次一:诊断工具
cProfile:找到最耗时的函数
import cProfile
import pstats
import io
import math
def is_prime(n: int) –> bool:
"""朴素素数判定——故意用低效实现做演示"""
if n < 2:
return False
for i in range(2, int(math.sqrt(n)) + 1):
if n % i == 0:
return False
return True
def find_primes(limit: int) –> list[int]:
"""找出范围内所有素数"""
primes = []
for n in range(limit):
if is_prime(n):
primes.append(n)
return primes
def main():
return find_primes(100_000)
# ===== 方式一:命令行 =====
# python -m cProfile -s cumtime script.py
# ===== 方式二:代码内 profile =====
if __name__ == "__main__":
profiler = cProfile.Profile()
profiler.enable()
result = main()
profiler.disable()
s = io.StringIO()
ps = pstats.Stats(profiler, stream=s).sort_stats("cumulative")
ps.print_stats(10) # 只显示前 10 个
print(s.getvalue())
输出示例:
ncalls tottime percall cumtime percall filename:lineno(function)
1 0.000 0.000 0.423 0.423 script.py:17(find_primes)
100000 0.413 0.000 0.413 0.000 script.py:8(is_prime)
1 0.000 0.000 0.423 0.423 script.py:24(main)
| ncalls | 调用次数(100000/1 表示 100000 次调用,1 次递归) |
| tottime | 函数自身耗时(不含子调用) |
| cumtime | 累计耗时(含所有子调用) |
关键发现:is_prime 被调用了 100000 次,累计 0.423 秒——这就是瓶颈。优化目标明确。
line_profiler:逐行分析
cProfile 告诉你哪个函数慢,line_profiler 告诉你函数内部哪一行慢:
# 安装:pip install line_profiler
@profile # ← 不需要 import,line_profiler 自动处理
def slow_computation(data: list[int]) –> dict[str, float]:
result = {}
for item in data:
processed = item ** 2 # 行 7
result[str(item)] = math.log(processed + 1) # 行 8
return result
data = list(range(10000))
slow_computation(data)
# 命令行运行:
# kernprof -l -v script.py
输出:
Line # Hits Time Per Hit % Time Line Contents
6 10001 1234.0 0.12 5.2 for item in data:
7 10000 2380.0 0.24 10.0 processed = item ** 2
8 10000 20150.0 2.02 84.8 result[str(item)] = math.log(processed + 1)
发现:第 8 行的 math.log 占了 84.8% 的时间。优化方向:预计算对数表、或用近似算法。
tracemalloc:内存泄漏与峰值定位
import tracemalloc
def memory_leak_demo():
"""演示 tracemalloc 如何定位内存泄漏"""
tracemalloc.start()
# 快照一:基准
snapshot1 = tracemalloc.take_snapshot()
# 模拟内存泄漏
leaked_data = []
for i in range(10000):
leaked_data.append([0] * 1000) # 每轮分配约 8KB
# 快照二:泄漏后
snapshot2 = tracemalloc.take_snapshot()
# 对比差异
stats = snapshot2.compare_to(snapshot1, "lineno")
print("Top 5 memory increases:")
for stat in stats[:5]:
print(f" {stat}")
print(f" +{stat.size_diff / 1024:.1f} KB, "
f"+{stat.count_diff} blocks")
print(f" {stat.traceback.format()[–1]}")
memory_leak_demo()
输出:
Top 5 memory increases:
script.py:15: size=78135 KiB (+78135 KiB), count=10000 (+10000)
+78135.0 KB, +10000 blocks
File "script.py", line 15
leaked_data.append([0] * 1000)
持续监控内存峰值
import tracemalloc
import time
def monitor_memory(target, args=(), duration: float = 10.0):
"""监控函数执行期间的内存峰值"""
tracemalloc.start()
start = time.perf_counter()
result = target(*args)
elapsed = time.perf_counter() – start
current, peak = tracemalloc.get_traced_memory()
tracemalloc.stop()
print(f"Function: {target.__name__}")
print(f" Duration: {elapsed:.2f}s")
print(f" Peak memory: {peak / 1024 / 1024:.1f} MiB")
print(f" Final memory: {current / 1024 / 1024:.1f} MiB")
return result
# 使用
def allocate_then_free():
data = [bytearray(1024 * 1024) for _ in range(50)] # 分配 50 MiB
time.sleep(1)
data.clear()
time.sleep(1)
monitor_memory(allocate_then_free)
层次二:算法优化
使用正确的数据结构
import time
import random
# === 场景:检查元素是否存在 ===
data = list(range(1_000_000))
check_values = [random.randint(0, 2_000_000) for _ in range(10_000)]
# ❌ 列表的 in 操作是 O(n)
start = time.perf_counter()
list_results = [v in data for v in check_values]
print(f"List search: {time.perf_counter() – start:.3f}s")
# ✅ 集合的 in 操作是 O(1)
data_set = set(data)
start = time.perf_counter()
set_results = [v in data_set for v in check_values]
print(f"Set search: {time.perf_counter() – start:.3f}s")
典型输出(差异可达 1000 倍):
List search: 1.234s
Set search: 0.001s
使用 collections.deque 替代 list.pop(0)
from collections import deque
import time
n = 100_000
# ❌ list.pop(0) 是 O(n)——后面的元素都要前移
lst = list(range(n))
start = time.perf_counter()
while lst:
lst.pop(0)
print(f"list.pop(0): {time.perf_counter() – start:.3f}s")
# ✅ deque.popleft() 是 O(1)
dq = deque(range(n))
start = time.perf_counter()
while dq:
dq.popleft()
print(f"deque.popleft: {time.perf_counter() – start:.3f}s")
使用 functools.lru_cache 缓存重复计算
from functools import lru_cache
import time
# ❌ 无缓存——每次递归都重新计算
def fib_naive(n: int) –> int:
if n < 2:
return n
return fib_naive(n – 1) + fib_naive(n – 2)
# ✅ LRU 缓存——相同参数只计算一次
@lru_cache(maxsize=None)
def fib_cached(n: int) –> int:
if n < 2:
return n
return fib_cached(n – 1) + fib_cached(n – 2)
# 对比
start = time.perf_counter()
result = fib_cached(35)
print(f"Cached fib(35): {time.perf_counter() – start:.6f}s")
# 对于无缓存版本,35 已经是灾难级别——这里只演示缓存的威力
使用生成器替代列表
import sys
# ❌ 一次加载所有数据到内存
def read_lines_list(filename: str) –> list[str]:
with open(filename) as f:
return f.readlines()
# ✅ 惰性逐行读取
def read_lines_generator(filename: str):
with open(filename) as f:
for line in f:
yield line.strip()
# 内存对比
lines_list = read_lines_list("large_file.txt")
lines_gen = read_lines_generator("large_file.txt")
print(f"List size: {sys.getsizeof(lines_list)} bytes") # 可能几百 MB
print(f"Generator size: {sys.getsizeof(lines_gen)} bytes") # 约 200 bytes
循环优化:将不变计算提到循环外
import math
# ❌ 每次迭代都计算 len(data) 和 math.sqrt
def slow_loop(data: list[float]) –> list[float]:
return [x * math.sqrt(len(data)) for x in data]
# ✅ 循环不变量提前计算
def fast_loop(data: list[float]) –> list[float]:
factor = math.sqrt(len(data))
return [x * factor for x in data]
层次三:底层加速
numba:JIT 编译提速
import numba
import numpy as np
import time
# ===== 纯 Python =====
def monte_carlo_pi_python(n: int) –> float:
"""蒙特卡洛法计算 π"""
inside = 0
for _ in range(n):
x = np.random.random()
y = np.random.random()
if x * x + y * y <= 1.0:
inside += 1
return 4.0 * inside / n
# ===== Numba JIT =====
@numba.jit(nopython=True)
def monte_carlo_pi_numba(n: int) –> float:
inside = 0
for _ in range(n):
x = np.random.random()
y = np.random.random()
if x * x + y * y <= 1.0:
inside += 1
return 4.0 * inside / n
# 对比(首次运行 Numba 有编译开销,所以先预热)
n = 10_000_000
# 纯 Python
start = time.perf_counter()
pi_py = monte_carlo_pi_python(n)
print(f"Pure Python: {time.perf_counter() – start:.2f}s, pi ≈ {pi_py}")
# Numba(预热后)
_ = monte_carlo_pi_numba(100)
start = time.perf_counter()
pi_nb = monte_carlo_pi_numba(n)
print(f"Numba JIT: {time.perf_counter() – start:.2f}s, pi ≈ {pi_nb}")
print(f"Speedup: {(time.perf_counter() – start):.0f}x") # 通常 10~100x
Cython:Python 的超集
# calc.pyx —— Cython 源文件
# pip install cython
# 编译:python setup.py build_ext –inplace
def sum_of_squares(int n):
"""Cython 编译的平方和计算"""
cdef int i
cdef long long total = 0
for i in range(n):
total += i * i
return total
# setup.py
from setuptools import setup
from Cython.Build import cythonize
setup(
ext_modules=cythonize("calc.pyx"),
)
# 使用编译后的模块
from calc import sum_of_squares
result = sum_of_squares(10_000_000)
print(result)
Cython 的核心思想:在 Python 语法中添加 C 类型声明(cdef int i),编译为 C 扩展后获得接近 C 的性能。
使用 __slots__ 减少内存
在 Python 进阶 #20:__slots__ 进阶 中已详细讨论过——大量小对象场景下,__slots__ 可减少 50% 以上的内存占用:
class PointSlots:
__slots__ = ("x", "y")
def __init__(self, x, y):
self.x = x
self.y = y
class PointDict:
def __init__(self, x, y):
self.x = x
self.y = y
# 100 万个 Point,__slots__ 节省约 100 MiB
性能优化决策树
#mermaid-svg-u9y2sEIzH5cv9p2q{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-u9y2sEIzH5cv9p2q .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-u9y2sEIzH5cv9p2q .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-u9y2sEIzH5cv9p2q .error-icon{fill:#552222;}#mermaid-svg-u9y2sEIzH5cv9p2q .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-u9y2sEIzH5cv9p2q .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-u9y2sEIzH5cv9p2q .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-u9y2sEIzH5cv9p2q .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-u9y2sEIzH5cv9p2q .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-u9y2sEIzH5cv9p2q .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-u9y2sEIzH5cv9p2q .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-u9y2sEIzH5cv9p2q .marker{fill:#333333;stroke:#333333;}#mermaid-svg-u9y2sEIzH5cv9p2q .marker.cross{stroke:#333333;}#mermaid-svg-u9y2sEIzH5cv9p2q svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-u9y2sEIzH5cv9p2q p{margin:0;}#mermaid-svg-u9y2sEIzH5cv9p2q .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-u9y2sEIzH5cv9p2q .cluster-label text{fill:#333;}#mermaid-svg-u9y2sEIzH5cv9p2q .cluster-label span{color:#333;}#mermaid-svg-u9y2sEIzH5cv9p2q .cluster-label span p{background-color:transparent;}#mermaid-svg-u9y2sEIzH5cv9p2q .label text,#mermaid-svg-u9y2sEIzH5cv9p2q span{fill:#333;color:#333;}#mermaid-svg-u9y2sEIzH5cv9p2q .node rect,#mermaid-svg-u9y2sEIzH5cv9p2q .node circle,#mermaid-svg-u9y2sEIzH5cv9p2q .node ellipse,#mermaid-svg-u9y2sEIzH5cv9p2q .node polygon,#mermaid-svg-u9y2sEIzH5cv9p2q .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-u9y2sEIzH5cv9p2q .rough-node .label text,#mermaid-svg-u9y2sEIzH5cv9p2q .node .label text,#mermaid-svg-u9y2sEIzH5cv9p2q .image-shape .label,#mermaid-svg-u9y2sEIzH5cv9p2q .icon-shape .label{text-anchor:middle;}#mermaid-svg-u9y2sEIzH5cv9p2q .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-u9y2sEIzH5cv9p2q .rough-node .label,#mermaid-svg-u9y2sEIzH5cv9p2q .node .label,#mermaid-svg-u9y2sEIzH5cv9p2q .image-shape .label,#mermaid-svg-u9y2sEIzH5cv9p2q .icon-shape .label{text-align:center;}#mermaid-svg-u9y2sEIzH5cv9p2q .node.clickable{cursor:pointer;}#mermaid-svg-u9y2sEIzH5cv9p2q .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-u9y2sEIzH5cv9p2q .arrowheadPath{fill:#333333;}#mermaid-svg-u9y2sEIzH5cv9p2q .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-u9y2sEIzH5cv9p2q .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-u9y2sEIzH5cv9p2q .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-u9y2sEIzH5cv9p2q .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-u9y2sEIzH5cv9p2q .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-u9y2sEIzH5cv9p2q .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-u9y2sEIzH5cv9p2q .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-u9y2sEIzH5cv9p2q .cluster text{fill:#333;}#mermaid-svg-u9y2sEIzH5cv9p2q .cluster span{color:#333;}#mermaid-svg-u9y2sEIzH5cv9p2q div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-u9y2sEIzH5cv9p2q .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-u9y2sEIzH5cv9p2q rect.text{fill:none;stroke-width:0;}#mermaid-svg-u9y2sEIzH5cv9p2q .icon-shape,#mermaid-svg-u9y2sEIzH5cv9p2q .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-u9y2sEIzH5cv9p2q .icon-shape p,#mermaid-svg-u9y2sEIzH5cv9p2q .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-u9y2sEIzH5cv9p2q .icon-shape .label rect,#mermaid-svg-u9y2sEIzH5cv9p2q .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-u9y2sEIzH5cv9p2q .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-u9y2sEIzH5cv9p2q .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-u9y2sEIzH5cv9p2q :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
CPU 密集型循环/计算
I/O 密集型网络/文件/数据库
内存密集型大量对象/大文件
是
否
数值计算
需要完整 C 控制
调用已有 C 库
性能不满足要求
运行 cProfile定位瓶颈函数
瓶颈类型?
算法优化数据结构、缓存、NumPy
并发优化asyncio、线程池
内存优化__slots__、生成器、mmap
算法优化后满足需求?
完成 ✅
热路径是否适合编译?
Numba JIT简单装饰器,10x+
CythonPython 超集,编译为 .so
ctypes / cffi直接调用 .dll / .so
性能优化的工程原则
1. 测量 → 优化 → 测量
每次优化前先取基准,优化后验证效果:
import time
def benchmark(func, *args, runs: int = 5):
"""运行多次取最小值,排除系统抖动"""
times = []
for _ in range(runs):
start = time.perf_counter()
func(*args)
times.append(time.perf_counter() – start)
return min(times), sum(times) / len(times)
baseline_min, baseline_avg = benchmark(slow_function, data)
# … 优化 slow_function …
optimized_min, optimized_avg = benchmark(fast_function, data)
print(f"Baseline: {baseline_avg:.3f}s (min: {baseline_min:.3f}s)")
print(f"Optimized: {optimized_avg:.3f}s (min: {optimized_min:.3f}s)")
print(f"Speedup: {baseline_min / optimized_min:.1f}x")
2. 优化收益递减规律
# 优化投入 vs 回报
# 阶段一:算法优化(数据结构、缓存) → 10x ~ 1000x
# 阶段二:避免 Python 层面的浪费 → 2x ~ 10x
# (预计算、循环外提、生成器)
# 阶段三:编译加速(Numba、Cython) → 5x ~ 50x
# 阶段四:C 扩展重写核心逻辑 → 2x ~ 5x 在阶段三之上
# 阶段五:汇编级优化 → 微乎其微,几乎不值得
3. 不要过早优化
Donald Knuth 的名言值得铭记:“过早优化是万恶之源”——但不是"不要优化",而是在优化之前先有正确的实现和完整的测试。没有测试的优化是赌博:无法验证优化后的代码是否行为一致。
# 优化之前必须有的基础设施:
# 1. 完整的单元测试(验证行为一致性)
# 2. 性能基准测试脚本(量化效果)
# 3. 性能回归检测(CI 中监控性能退化)
# 在 CI 中监测性能退化:
# pytest-benchmark 可以自动比较本次运行与上一次的性能
综合实战:日志分析器性能优化
从慢到快的完整优化过程:
"""日志分析器——展示从 30 秒到 0.5 秒的优化过程"""
import re
import time
from collections import Counter
from typing import Iterator
# ===== 版本零:原始实现(基准) =====
def parse_log_original(filename: str) –> dict[str, int]:
"""逐行读取,正则匹配,字典统计"""
pattern = re.compile(r'\\[(ERROR|WARNING|INFO)\\]')
counts: dict[str, int] = {}
with open(filename) as f:
for line in f:
match = pattern.search(line)
if match:
level = match.group(1)
counts[level] = counts.get(level, 0) + 1
return counts
# ===== 版本一:优化数据结构(Counter 替代手写计数) =====
def parse_log_v1(filename: str) –> dict[str, int]:
"""Counter 替代手写计数——代码更简洁,性能微提"""
pattern = re.compile(r'\\[(ERROR|WARNING|INFO)\\]')
counter: Counter[str] = Counter()
with open(filename) as f:
for line in f:
match = pattern.search(line)
if match:
counter[match.group(1)] += 1
return dict(counter)
# ===== 版本二:预编译 + 快速路径 =====
def parse_log_v2(filename: str) –> dict[str, int]:
"""预编译正则 + 快速路径跳过无级别行"""
pattern = re.compile(r'\\[(ERROR|WARNING|INFO)\\]')
counter: Counter[str] = Counter()
with open(filename) as f:
for line in f:
# 快速路径:如果行不包含 '[',直接跳过
if '[' not in line:
continue
match = pattern.search(line)
if match:
counter[match.group(1)] += 1
return dict(counter)
# ===== 版本三:行级缓冲区 + 多行读取 =====
def parse_log_v3(filename: str, chunk_size: int = 65536) –> dict[str, int]:
"""大块读取 + 多行处理"""
pattern = re.compile(r'\\[(ERROR|WARNING|INFO)\\]')
counter: Counter[str] = Counter()
with open(filename) as f:
remainder = ""
while True:
chunk = f.read(chunk_size)
if not chunk:
break
lines = (remainder + chunk).split("\\n")
remainder = lines.pop() # 最后一行可能不完整
for line in lines:
if "[" not in line:
continue
match = pattern.search(line)
if match:
counter[match.group(1)] += 1
return dict(counter)
# ===== 基准测试 =====
if __name__ == "__main__":
# 生成 100 万行日志文件
import random
levels = ["INFO", "INFO", "INFO", "INFO", "WARNING", "WARNING", "ERROR"]
with open("test.log", "w") as f:
for i in range(1_000_000):
level = random.choice(levels)
f.write(f"2024-01-01 12:00:{i % 60:02d} [{level}] Message {i}\\n")
for name, func in [
("original", parse_log_original),
("v1 (Counter)", parse_log_v1),
("v2 (+skip)", parse_log_v2),
("v3 (+chunk)", parse_log_v3),
]:
start = time.perf_counter()
result = func("test.log")
elapsed = time.perf_counter() – start
print(f"{name:>15}: {elapsed:.3f}s → {result}")
工具箱速查
| cProfile | CPU 性能分析 | python -m cProfile -s cumtime script.py |
| line_profiler | 逐行性能分析 | kernprof -l -v script.py |
| tracemalloc | 内存追踪 | tracemalloc.start() + take_snapshot() |
| memory_profiler | 逐行内存分析 | python -m memory_profiler script.py |
| py-spy | 采样 profiler(无需修改代码) | py-spy top — python script.py |
| timeit | 微基准测试 | python -m timeit -s "setup" "stmt" |
| numba | JIT 编译 | @numba.jit(nopython=True) |
| cython | Python → C 编译 | .pyx 文件 + cythonize() |
| ctypes | 调用 C 函数 | ctypes.CDLL("./lib.so") |
| cffi | 调用 C 函数(更 Pythonic) | ffi.cdef("int func(int);") |
优化优先级的黄金法则
系列结语
从 Python 基础的第一行 print("Hello World"),到本文的 C 扩展加速——Python 进阶系列的 30 篇文章涵盖了从"会用"到"用好"的全路径:闭包与装饰器、迭代器与生成器、上下文管理器、魔术方法与运算符重载、描述符与属性访问、类型注解与工程化、并发与异步进阶。
每一篇都在回答同一个问题:Python 为什么是这样设计的?——背后是面向对象的对象模型、是协程调度的事件循环、是类型系统在灵活与安全之间的权衡。
性能优化是工程的最后一公里。它让前面所有的知识都找到了落点——理解了数据模型才能写出对缓存友好的代码,理解了解释器机制才知道什么时候该跳出 Python 用 C 扩展。
如果这 30 篇文章对 Python 进阶之路有帮助,点赞收藏让更多人看到!关注专栏,更多技术系列持续更新中。


