Python profile 和 cProfile 的计时开销有什么区别
profile 的纯 Python 实现会给被测程序带来较明显的事件处理开销;cProfile 使用 C 扩展,通常更适合日常定位热点。但“开销较低”不等于零开销,也不能给出对所有程序通用的减速倍数。两者报表用于找时间花在哪里,比较代码方案快慢应另做无剖析计时。
Python 剖析工具官方文档:https://docs.python.org/3.14/library/profile.html
本文示例明确采用 CPython 3.14 的接口。较新的 Python 文档对工具组织和弃用状态已有调整:当前 profile 文档 标注其自 3.15 弃用、计划在 3.17 移除,并说明 cProfile 作为兼容别名保留。运行环境不同,应先看对应版本文档,不把本例当作所有版本的长期部署方案。
先把小项目的观察目标说清楚
一个文本汇总脚本可能没有复杂算法,却反复调用小函数。给它加上剖析器后,总耗时可能发生明显变化。如果据此认定业务函数突然变慢,就混入了观察工具自身的成本。
我会把这类判断拆为两项:先用 cProfile 看调用结构和高累计耗时函数,再脱离剖析器测原任务。这里表达的是工具选择方法,并非声称做过本文这组实测。小项目只输出输入一致的校验值、外部经过时间和函数报表,不预填任何性能数字。

用同一个任务接入两种剖析器
保存为 profiler_probe.py。代码为本文原创,未在制作过程中执行;读者运行时得到的数据才是对应环境的观察值。
import argparse
import cProfile
import platform
import profile
import pstats
import time
def token_score(text):
# 刻意保留小函数调用,以便观察调用密集型任务。
return len(text.strip().casefold())
def summarize(lines):
total = 0
for text in lines:
total += token_score(text)
return total
def main():
parser = argparse.ArgumentParser()
parser.add_argument("mode", choices=("baseline", "profile", "cprofile"))
args = parser.parse_args()
# 所有模式共用相同输入;数据准备与导入不在观察区间内。
lines = (" Alpha ", "Beta", " GAMMA ") * 20000
profiler = None
if args.mode == "profile":
profiler = profile.Profile()
elif args.mode == "cprofile":
profiler = cProfile.Profile()
start = time.perf_counter()
if profiler is None:
result = summarize(lines)
else:
# 两个类都支持 runcall,避免误用 profile 不支持的 enable。
result = profiler.runcall(summarize, lines)
elapsed = time.perf_counter() - start
print(platform.python_implementation(), platform.python_version())
print("mode=", args.mode, "checksum=", result, "elapsed_s=", elapsed)
if profiler is not None:
# 报表格式化放到外部计时结束后,单独观察函数累计时间。
pstats.Stats(profiler).strip_dirs().sort_stats(
pstats.SortKey.CUMULATIVE
).print_stats(8)
if __name__ == "__main__":
main()
# 在同一 CPython 3.14 环境分别运行;每条命令建立独立进程。 python profiler_probe.py baseline python profiler_probe.py profile python profiler_probe.py cprofile
输入构造、模块导入与报表打印都放在外部计时区间之外,便于围绕一次 summarize 调用讨论。剖析器对象也提前创建;因此这不是整进程启动时间比较。runcall 的包装开销仍计入带剖析器的外部耗时,正是此处希望观察的部分。
checksum 用来提示读者三个模式是否仍处理同一个任务,不是性能数据。某次结果不同,应先解释输入或逻辑差异,再谈耗时。单轮很短时不要把小数位差异直接解释为真实改进,也不要把一条结果当作工具固定开销。
解释开销为什么不只是一次读时钟
剖析器需要响应函数调用、返回等事件并维护统计。profile 在 Python 层处理这些工作,cProfile 的核心由 C 实现,成本不同。任务的函数调用密度也会影响观察扰动;大量短函数与少量长调用不能简单套同一个比例。这与 3.14 官方介绍 对两种实现的定位一致。

| 观察项 | 用途 | 不应作出的推论 |
|---|---|---|
| 外部 elapsed_s | 观察该调用区间的整体经过时间 | 等同于某一行报表的时间 |
| ncalls | 观察调用数量 | 调用最多就是最值得优化 |
| tottime | 关注函数自身耗时 | 包含其全部子调用成本 |
| cumtime | 关注函数连同子调用的累计耗时 | 各行相加等于总耗时 |
报表中上层与下层的累计时间会覆盖同一段调用,所以不能把 cumtime 列逐行累加为总量。本文没有给两个剖析器强行配置相同自定义时钟;外部统一使用 perf_counter,只保证外部观察区间的时钟口径一致,不能由此声称内部记账误差也完全相同。
把速度判断放回无剖析测量
如果目标是判断修改后的 summarize 是否更快,就把被测代码放在不启用任何剖析器的环境中。下面单独保存为 timing_probe.py,与前一个文件放在一起:
import timeit
from profiler_probe import summarize
# 使用同一份输入,脱离剖析器再观察任务耗时;不自动证明优化有效。
lines = (" Alpha ", "Beta", " GAMMA ") * 20000
samples = timeit.repeat(lambda: summarize(lines), number=10, repeat=5)
print("每轮十次调用的秒数:", samples)
根据 timeit 官方说明,repeat 返回多次计时结果,number 是每轮执行次数。此处每项是十次调用的总秒数,不是单次耗时;若展示单次值,应除以十并标明单位。timeit 默认暂时关闭垃圾回收,若任务依赖回收成本,应明确采用相应设置,而不是把结果直接等同于实际服务延迟。
至少随结果保留解释器版本、平台、输入规模和测量范围。同一机器交替观察不同模式与候选方案,可减少固定顺序带来的误判;仍需留意系统负载和预热。本文脚本只帮助读者形成可比较的记录,没有自动计算“profile 比 cProfile 慢几倍”的结论。
常见问题
profile 的校准能消除运行变慢吗?
校准用于补偿统计中的部分事件开销,不会让已发生的处理时间消失。它也不是可跨机器、跨计时器直接复用的万能常数。不需要研究或扩展纯 Python 剖析实现时,通常没有必要为日常热点定位选它。
cProfile 能直接证明 Python 写法不如 C 函数快吗?
不能直接根据带剖析器的结果做这种基准判断。两种实现被观测时的扰动可能不同,应把结论限制在热点线索,再用脱离剖析器、输入一致的测量回答速度问题。
示例可以放进生产请求里长期运行吗?
这份小项目用于开发环境理解开销和口径。它打印报表,也没有服务隔离、请求采样或并发治理设计,不能直接当作生产监控方案。
Python pstats 怎样同时限制性能报告的排序和显示条目
- 上一篇
- Python pstats 怎样同时限制性能报告的排序和显示条目
- 下一篇
- Python aifc 被移除后怎样选择 AIFF 音频处理路径
-
- 文章 · python教程 | 50分钟前 |
- Python importlib.metadata.entry_points 怎样按组选择插件入口
- 154浏览 收藏
-
- 文章 · python教程 | 52分钟前 |
- Python pkgutil.get_data 遇到不支持资源读取的加载器会返回什么
- 156浏览 收藏
-
- 文章 · python教程 | 55分钟前 |
- Python aifc 被移除后怎样选择 AIFF 音频处理路径
- 404浏览 收藏
-
- 文章 · python教程 | 59分钟前 |
- Python pstats 怎样同时限制性能报告的排序和显示条目
- 499浏览 收藏
-
- 文章 · python教程 | 1小时前 |
- Python tokenize.detect_encoding 怎样识别源文件编码声明
- 235浏览 收藏
-
- 文章 · python教程 | 1小时前 |
- Python mailbox.mbox 修改后为什么还要 flush 和 unlock
- 450浏览 收藏
-
- 文章 · python教程 | 1小时前 |
- Python tabnanny 怎样检查混合缩进的歧义
- 166浏览 收藏
-
- 文章 · python教程 | 1小时前 |
- Python mailbox.Maildir 怎样区分新邮件与已读邮件
- 343浏览 收藏
-
- 文章 · python教程 | 1小时前 |
- Python sched 怎样取消尚未触发的定时事件
- 132浏览 收藏
-
- 文章 · python教程 | 1小时前 |
- Python sched.scheduler 怎样让 run 不阻塞等待下个事件
- 164浏览 收藏
-
- 文章 · python教程 | 1小时前 |
- Python cmd.Cmd 的 precmd 和 postcmd 怎样修改命令循环
- 236浏览 收藏
-
- 文章 · python教程 | 1小时前 |
- Python cmd.Cmd 怎样为交互命令实现参数补全
- 182浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- PubMedQA
- 深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
- 777次使用
-
- H2O EvalGPT
- H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
- 773次使用
-
- LMArena
- LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
- 858次使用
-
- HELM
- 深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
- 824次使用
-
- MMBench
- MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
- 652次使用
-
- Python xml.dom.minidom.unlink 怎样释放不再使用的 DOM 树引用
- 2026-10-27 501浏览
-
- Python typing 怎样用 TypeIs 缩小类型
- 2026-10-24 501浏览
-
- Python sqlite3 Connection serialize 怎么导出数据库快照:备份窗口、内存占用与恢复校验
- 2026-08-26 501浏览
-
- Python监控网页状态:requests异常处理实战
- 2026-05-29 501浏览
-
- TensorFlow模型部署为API的TF Serving方法
- 2026-05-26 501浏览

