编程语言有两大衡量因素:开发速度和执行速度。Python
语言关注开发速度而非执行速度。你有可能会有疑惑,Python
代码总是能很快的完成任务,执行速度不快吗?很多情况下 Python
相对会慢很多,这时候你就需要找出滞后的位置和原因,并采取对应措施。
软件开发和工程领域有一句名言,"Measure, don’t guess",翻译成中文大致就是不要主观猜测,而要去测量。使用软件过程中如果出现问题,我们不应该假设问题的缘由,最好的方式应该是使用相对应的应用程序来测试,找到问题的真正原因。
Python
也提供了对应的工具,可以分析应用程序的性能,这些工具范围很广,从标准库中的简单单行程序到从正在运行的应用程序中收集数据的复杂框架。本文介绍其中最重要的九个,大多都支持跨平台运行。
Time and Timeit
最简单的分析功能是秒表分析,仅分析两段代码的执行时间。
Python
带有两个可用作秒表的功能函数的标准库。其中,Time
模块具备 perf_counter 功能,可以调用操作系统的高精度定时器,来获得时间戳。其基本原理是:在目标操作开始前,调用一次 time.perf_counter,然后在操作完成时,再调用一次,以获得两次的时间差。这是一种非常简便易行的时间获取方式。
而 Timeit
模块则是会对 Python
代码进行基准测试。Timeit
.Timeit
功能函数会截取一个代码段,运行多次(默认为 1 百万次)求平均值,来获得执行该操作所需的平均时间。可以用它来确定某个紧密的循环中,单一操作或函数的调用时长。例如,如果想判断一个列表解析式(list comprehension)与一个常规列表结构,那个执行更快(列表解析式通常更快)。
这两个模块都有明显的缺点, Time
它只是一个秒表,而 Timeit
的缺点在于:其主要用例是各个行或代码块上的各个细微标准差(microbenchmarks)。也就是说,仅当这些代码被单独处理时,这种比较才会有意义。因此,这两种方法都不足以对整个程序进行分析。一旦出现数千行的代码,这两种方法都会耗费您大量的时间。
cProfile
Python
标准库还带有一个整体程序分析器--cProfile
。当程序运行时,cProfile
会通过跟踪代码中的每个函数的调用,以生成一个包含了最常调用函数、以及平均调用时间的列表。
cProfile
有三大优势:
- 包含在标准库中,现有的
Python
安装包已经包含了cProfile
- 可以分析相关调用行为的多种不同信息。例如:它能够将函数调用自己的指令所花费的时间,与该函数所有的其他调用耗时区分开来。据此,可以判定出到底是该函数本身运行缓慢,还是在其他调用时出现的缓慢。
- 可以自由的约束
cProfile
。既可以对整个程序的运行进行采样,又可以仅在指定的函数运行时启用概要分析(toggle profiling)。通过缩小范围和去除分析时产生的“噪声”,您可以更好地关注该函数的功能与调用。
cProfile
也有明显的缺点:
- 默认情况下,它会设置多个采集点,生成大量的统计信息。
- 根据其执行模型,它在捕获每个函数调用时,都会产生大量的开销。因此
cProfile
不适合使用实时数据的方式,对生产环境中的应用程序进行性能分析,更适合于针对开发过程中的性能分析。
FunctionTrace
FunctionTrace
的工作原理与 cProfile
类似:无需向代码添加检测,只需将分析的脚本的名称传递给它即可,最后它会生成函数调用和内存使用情况的详细跟踪报告。此外,FunctionTrace
还可以处理多线程/多进程应用程序。
与 cProfile
类似,FunctionTrace
也没有使用采样功能,而是记录每一个动作,其分析组件基于 Rust 编写,开发人员表示,其对应用程序施加的开销小于 10%。
FunctionTrace
有两大优势:
- 跟踪数据为 JSON 格式,解析简单
- 可使用 Firefox Profiler (可以在任何支持 JavaScript 的浏览器中运行,而不仅仅是 Firefox)将结果呈现为交互式图形。
Palanteer
Palanteer
可用于分析 Python
和 C++ 程序。
Palanteer
有很多优势:
Palanteer
还可以在桌面上运行的 GUI 应用程序中实时显示检测结果。- 利用
Palanteer
来检测Python
程序非常简单,函数调用、异常、垃圾收集和操作系统级别的内存分配都可以被跟踪。如果应用程序性能问题与内存使用或者对象分配有关,最后两项将特别有用。
Palanteer
也有明显的缺点,如果你想使用它必须完全从源代码开始构建它。
Pyinstrument
Pyinstrument
与 cProfile
的工作方式类似,能够通过跟踪目标程序,以报告的形式,反映出那些占用了大部分运行时间的代码。不过,与 cProfile
相比,Pyinstrument
的优点主要有两点:
Pyinstrument
不会去挂钩(hook)函数调用的每个实例,而是会以毫秒为间隔,对程序的调用栈进行采样,因此能够灵敏地检测出程序中最耗费运行时的部分。Pyinstrument
的报告要简洁很多。它能够通过突出显示程序中占用时间最多的函数,以便您能尽快地发现问题,并专注分析原因。Pyinstrument
同样具有 cProfile
的各种优点,可以将它用作应用程序中的对象,来记录所选功能,而不是整个程序的行为。Pyinstrument
还提供包括 HTML 格式在内的多种输出形式。当然,
此外,如下两个方面值得您的注意:
某些通过 C 编译的扩展程序(例如使用 Cython 创建的程序),会在通过命令行进行 Pyinstrument
调用时,可能无法正常工作。不过,如果您在程序本身使用了 Pyinstrument
,例如:通过使用 Pyinstrument
分析器的调用包装了 main() 函数,那么它们还是能够正常工作的。Pyinstrument
不能很好地处理在多个线程中运行的代码。此时,您可能需要考虑使用下面将要介绍到的 Py-spy
。
Py-spy
Py-spy
在工作原理与 Pyinstrument
一样,都是定期采集程序调用栈的状态,而不是记录每一个调用。与 Pyinstrument
不同的是,Py-spy
使用 Rust 编写的核心组件(而 Pyinstrument
使用 C 扩展),运行的是带有分析程序的外进程(out-of-process),因此它可以安全地与生产环境中的代码协同使用。
Py-spy
能够轻松地完成许多其他分析工具无法实现的任务,其中包括:分析多线程或带有子处理(subprocessed)机制的 Python
程序等。此外,Py-spy
也可以分析那些使用符号进行过编译的 C 扩展程序。而对于使用了 Cython 编译的扩展程序,Py-spy
需要使用对应生成的 C 文件,以便收集正确的跟踪信息。
利用 Py-spy
检查应用程序有如下两种基本方法,来:
- 使用
Py-spy
的 record 命令,并在运行结束后会生成火焰图(flame graph)。 - 使用
Py-spy
的 top 命令,通过实时更新,交互式地显示Python
应用程序的内部,并以与 Unix 的 top 工具相同的方式显示信息。而且那些单线程栈也可以通过命令行被显示出来。
但是,Py-spy
有一个很大的却带你:主要适用于从外部分析整个程序、或是某些组件,不适合对某个特定的功能函数进行采样。
Snakeviz
cProfile
不支持可视化功能,通常使用 pstats
来实现其跟踪文件的可视化,但是 pstats
生成的可视化有时并非是你所需要的。
Snakeviz
可以利用 cProfile
生成的数据并生成易于阅读的交互式图形,最终以 HTML 呈现。目前提供两种可用的交互式图形: 每种图形都可以清晰的显示各部分所用的时间。图表的每一段代表一个函数的调用时间。单击一个段可以检查它下面的堆栈中的所有内容所花费的时间。
Snakeviz
还生成可搜索和可排序的 HTML 表格视图;相较于 pstats
,可创建的跟踪的更具交互性的版本。
Yappi
Yappi
是 Yet Another Python Profiler (另一个 Python
分析工具)的缩写。它在功能上,较上述讨论过的工具库只多不少。在默认情况下,PyCharm 自带 Yappi
,因此使用该 IDE 的用户中已经具备对于 Yappi
的内置访问权限。
Yappi
的使用需要用指令来修饰目标代码,以便针对分析机制进行调用,启动,停止和生成报告。Yappi
允许根据测试的实际需求,在经过时间(wall time)或 CPU 时间之间进行选择。前者只是一个秒表;后者则可以通过系统原生 API,记录下 CPU 实际执行代码过程中的用时,以便调整 I/O 的暂停或线程的休眠。通过 CPU 时间可以更加精确地了解某些操作(例如:数字代码的执行)的实际用时。
Yappi
提供了 Yappi.get_thread_stats()
函数,该函数可以记录任何一个线程活动,检索出对应的统计信息,并分别对其进行分析。而且可以对统计数据进行过滤和细粒度的排序,实现类似于 cProfile
中的操作。
此外,Yappi
还可以分析 greenlet 和 coroutine (协程)。作为一种分析并发代码的强大工具,被广泛地用来分析异步 metaphor 。