首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >关于并行的定律与Python编程性能分析工具

关于并行的定律与Python编程性能分析工具

原创
作者头像
Shalom听雨来
发布2026-09-14 17:03:58
发布2026-09-14 17:03:58
70
举报

简介 1. 阿姆达尔定律 2. 古斯塔夫森定律 3. Python 中的编程性能分析工具

阿姆达尔定律(Amdahl's Law)

阿姆达尔定律由G.M. Amdahl在1967年提出,用于描述并行处理系统的加速比。其核心思想是:系统的加速比受限于程序中必须串行执行的部分。公式如下:

speedup = \frac{1}{(1-p)+\frac{p}{N}}

其中p 指的是可并行化的代码的时间占比,N 指的是处理器的数量。阿姆达尔定律可以非常粗略的估计并行化的程序的潜在加速比。阿姆达尔定律考虑的是固定规模问题下速度的上限。

古斯塔夫森定律 (Gustafson's Law)

古斯塔夫森定律则从可扩展问题(scaled problem)的角度出发,认为随着处理器数量增加,可以处理更大的问题,从而获得更高的加速比。公式如下:

speedup = \frac{a+ n*b}{a+b}

其中a 是必须串行部分的代码执行时间,b 是可以并行的部分代码执行时间,n 是处理器数量。

F 表示必须串行部分代码的时间占比:

F=\frac{a}{a+b}

那么会有:

speedup = F + n * (1 - F)

从公式中可以看出,F (串行化程度)足够小,也即并行化足够高,那么加速比和cpu个数成正比。

Python 中的编程性能分析工具

简介

下面这套流程是我在沙盒里实跑验证过的(Python 3.10 + line_profiler 5.0.2 + memory_profiler 0.61),所有输出都是真实结果,不是示例文本。

一、先选对工具

工具

粒度

依赖

典型开销

什么时候用

cProfile

函数级

内置

第一步:全局扫描,找出"哪个函数慢"

line_profiler

逐行时间

pip 装

(10–30×) 第二步:热点函数里"哪一行慢"

memory_profiler

逐行内存

pip 装

很高

(10–100×) 内存涨/泄漏时,定位"哪一行吃内存"

使用步骤建议:cProfile 定位函数 → line_profiler 定位行 → memory_profiler 查内存。一上来就逐行 profile 全程序,程序会慢到不可用的程度。

cProfile:零依赖全局扫描

命令行(最常用)

代码语言:txt
复制
python3 -m cProfile -s cumtime demo_slow.py      # 按累计耗时排序
python3 -m cProfile -s tottime demo_slow.py      # 按自身耗时排序(排除子调用)
python3 -m cProfile -o demo.prof demo_slow.py    # 落盘,之后离线分析

实测输出:

代码语言:txt
复制
 215034 function calls (63925 primitive calls) in 0.361 seconds
    Ordered by: cumulative time
    ncalls  tottime  percall  cumtime  percall filename:lineno(function)
1    0.002    0.002    0.361    0.361 demo_slow.py:1(<module>)
1    0.002    0.002    0.287    0.287 demo_slow.py:33(main)
1    0.007    0.007    0.210    0.210 demo_slow.py:19(process)
1    0.201    0.201    0.201    0.201 {built-in method time.sleep}
150049/1    0.027    0.000    0.027    0.027 demo_slow.py:11(fib)

字段含义:

• ncalls:调用次数。150049/1 是"总次数/非递归次数",斜杠说明这是递归函数。

• tottime:函数自身耗时,不含它调用的子函数 —— 找"自己就很慢"的函数用它排。

• cumtime:累计耗时,含所有子调用 —— 找"这条调用链拖慢了整体"用它排。

• percall:tottime/ncalls。

一眼看出:process 的 cumtime 0.210s 里,0.201s 全在 time.sleep —— sleep/IO 等待也算进 cumtime,这不是 CPU 瓶颈。这就是 cumtime 最大的陷阱。

cProfile 的盲区:看不到行;C 扩展(numpy/pandas 内部)不展开;多线程默认只测主线程;开销会让小函数失真。

line_profiler:逐行耗时

下载方式

代码语言:txt
复制
pip install line_profiler

  • 用法 1:kernprof(推荐)

被 profiling 的函数加 @profile 装饰器,不需要 import 任何东西 —— kernprof 会把 profile 注入 builtins。

代码语言:txt
复制
@profile
def process(rows):

raw = ""
for i in range(rows):
    raw += str(i) + ","      # 字符串不可变,O(n²)
time.sleep(0.2)

代码语言:txt
复制
kernprof -l -v demo_slow.py          # -l 逐行,-v 跑完立刻打印
kernprof -l demo_slow.py             # 只生成 demo_slow.py.lprof
python3 -m line_profiler demo_slow.py.lprof    # 之后随时离线查看

实测输出:

代码语言:txt
复制
Timer unit: 1e-06 s
Total time: 0.218204 s
Function: process at line 13
Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
16         1          0.6      0.6      0.0      raw = ""
18     20001       2235.3      0.1      1.0      for i in range(rows):
19     20000       5429.0      0.3      2.5          raw += str(i) + ","
20         1        631.6    631.6      0.3      parts = raw.split(",")
23         1     201208.3 201208.3     92.2      time.sleep(0.2)

Hits:该行执行次数(循环体行会很大)

Time:该行总耗时;Per Hit:单次耗时 → 判断"是跑得太多还是单次太慢"

• % Time:占该函数比例

⚠️ 每次都先看 Timer unit:上面是 1e-06 s,但用显式 API 打印时我实测得到的是 Timer unit: 1e-09 s(纳秒),数字会差 1000 倍。别跨报告直接比绝对值,只看百分比。

  • 用法 2:不侵入业务代码(显式 API,适合生产代码)
代码语言:txt
复制
from line_profiler import LineProfiler
import demo_slow
lp = LineProfiler()
lp.add_function(demo_slow.process)   # 只盯这一个函数,开销最小
lp(demo_slow.main)()                 # 或用 with lp: 包裹代码块
lp.print_stats()

直接 python3 demo_slow.py 会因 @profile 未定义报 NameError。加个兜底:

代码语言:txt
复制
try:      # line_profiler >= 4.1:不用 kernprof 运行时,profile 是透明的空装饰器
from line_profiler import profile
except ImportError:
def profile(func):
    return func

memory_profiler:逐行内存

下载步骤:

代码语言:txt
复制
pip install memory_profiler psutil     # psutil 让采样快很多

逐行报告

代码语言:txt
复制
from memory_profiler import profile    # 注意:这里必须显式 import
@profile
def main():
rows = build_rows(120000)
text = [str(i) for i in range(200000)]
del rows                           # 释放后能看到内存下降
代码语言:txt
复制
python3 demo_mem.py                    # 有装饰器时直接跑就会打印
python3 -m memory_profiler demo_mem.py # 或显式指定

实测输出:

代码语言:txt
复制
Line #    Mem usage    Increment  Occurrences   Line Contents
=============================================================
16     65.1 MiB     31.0 MiB           1       rows = build_rows(120000)
17     78.8 MiB     13.8 MiB      200003       text = [str(i) for i in range(200000)]
18     51.4 MiB    -27.4 MiB           1       del rows
19     51.4 MiB      0.0 MiB           1       keep = text[: len(text) // 2]

Mem usage:执行完该行后进程的 RSS

Increment:相对上一行的净增量(不是累计分配量!所以 del 行是负数,这正是找"内存没释放"的关键)

Occurrences:该行执行次数

时间曲线(看泄漏最直观)

代码语言:txt
复制
mprof run -o mem.dat demo_mem.py     # 不需要任何装饰器,每 0.1s 采样一次 RSS
mprof plot -o mem_curve.png mem.dat  # 出图(需 matplotlib)

其他可用工具

• py-spy:py-spy top --pid <pid> / py-spy record,采样式,能直接分析正在跑的生产进程,开销 <1%,无需改代码

• scalene:同时给出 CPU / GPU / 内存,且能区分 Python 代码与 C 扩展耗时,精度远高于 memory_profiler

• pyinstrument:调用树式输出,比 cProfile 的扁平列表易读

• tracemalloc:内置,查"哪一行分配了最多 Python 对象",内存问题的首选交叉验证手段

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 阿姆达尔定律(Amdahl's Law)
  • 古斯塔夫森定律 (Gustafson's Law)
  • Python 中的编程性能分析工具
    • 简介
    • cProfile:零依赖全局扫描
    • line_profiler:逐行耗时
    • memory_profiler:逐行内存
    • 其他可用工具
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档