Python 代码剖析2024 年 8 月 29 日 | 阅读 12 分钟 严肃的软件开发需要性能优化。在优化应用程序性能时,我们无法回避剖析器。剖析器通过监控生产服务器或跟踪方法调用的频率和持续时间来发挥作用。以下教程将介绍 Python 剖析器的基本用法,分解关键概念,并介绍 Python 剖析中每个关键概念的不同库和工具。 首先,我们将列出 Python 剖析中的所有关键概念。然后,我们将把每个关键概念分解为三个主要部分:
APM 工具非常适合剖析 Web 应用程序事务的完整生命周期。大多数 APM 工具可能不是用 Python 编程语言编写的;然而,无论 Web 应用程序是用什么语言编写的,它们都能很好地工作。 在我们开始之前,请注意,我们将主要关注基于 Python 3 的示例。 因此,让我们开始吧。 理解跟踪官方上,跟踪被认为是日志记录的一种特殊用例,用于记录与程序执行相关的信息。由于这种用例与事件日志记录非常相似,因此事件日志记录和跟踪之间的区别并不明确。事件日志记录似乎是系统管理员的理想选择,而软件开发人员更关心使用跟踪来调试软件程序。让我们用一句话来理解跟踪——即软件开发人员利用日志记录来记录与软件执行相关的数据。在 Python 的开源标准库中,trace 和 faulthandler 模块允许我们使用基本的跟踪。 现在让我们详细了解这两个模块的工作原理。 理解 Python trace 模块Python 的 trace 模块旨在“检查程序运行时执行了哪些语句和函数,以生成分析和调用图信息”。Python trace 模块的文档提供的信息不多;然而,Python Module of the Week (PyMOTW) 对该模块有一个简洁的描述。trace 会“跟踪 Python 语句的执行”。 现在让我们看一个示例来理解 trace 模块的执行。 示例 输出 This is the main function recurse(2) recurse(1) recurse(0) 说明 在上面的代码片段中,我们定义了 main() 函数,在其中 recurse() 函数会调用自身,直到 lvl 参数达到零。然后我们定义了带有 lvl 参数的 recurse() 函数。最后,我们定义了一个名为 notCalled() 的函数,因为它在程序中永远不会被调用。 现在让我们看看上面程序的跟踪执行。 跟踪执行 说明 直接从命令行使用 trace 模块非常容易。当提供 --trace 选项时,会打印出程序运行时正在执行的语句。此示例还忽略了 Python 标准库的位置,以避免跟踪到 importlib 和其他模块。 我们还可以使用 Python trace 模块执行许多操作。其中一些如下:
理解 Python faulthandler 模块相比之下,faulthandler 模块拥有稍好的 Python 文档。它指出 faulthandler 模块的目的是在发生故障时、超时后或收到用户信号时显式转储 Python 堆栈跟踪。它还与其他系统故障处理程序(如 Apport 或 Windows 故障处理程序)配合使用。faulthandler 和 trace 模块都提供更多的跟踪功能,可以帮助我们调试 Python 代码。我们将在下一节中看到更多剖析统计信息。 如果有人是跟踪初学者,建议从简单使用 trace 模块开始。 开源 APM 选项APM 选项有不同的工具可用,例如 Jaeger 和 Zipkin。尽管它们不是用 Python 编程语言编写的,但它们对 Web 和分布式应用程序非常有效。Jaeger 官方支持 Python,并且是 Cloud Native Computing Foundation 的一部分。它拥有更广泛的部署文档。由于这些原因,如果您想在分布式 Web 架构中跟踪请求,也建议从 Jaeger 开始。如果它不适合您的跟踪需求,您可以选择 Zipkin。 应该剖析代码的哪个部分?现在,让我们深入探讨剖析的细节。术语“剖析”主要用于性能测试,性能测试的目标是通过深入分析来查找瓶颈。因此,我们可以利用跟踪工具来支持我们进行剖析。请记住,跟踪是软件开发人员记录的有关软件执行的信息。因此,记录性能指标也是一种执行剖析分析的方法。 然而,我们并不局限于跟踪。随着剖析在主流中越来越受关注,现在我们有了直接执行剖析的工具。现在的问题是,我们剖析软件的哪个部分(测量其性能指标)? 通常,我们剖析
在深入研究这些内容并提供通用 Python 和 APM 选项之前,让我们探讨一下用于剖析的指标类型和剖析技术本身。 应该剖析哪些指标?速度(时间)通常,我们需要在剖析时测量执行每个方法所花费的时间。每当我们使用方法剖析工具(如 cProfile(Python 语言中可用))时,方法的计时指标可以显示统计信息,例如调用次数(显示为 ncalls)、函数中花费的总时间(tottime)、每次调用的时间(tottime/ncalls 并显示为 percall)、函数中花费的累计时间(cumtime)以及每次调用的累计时间(cumtime 除以原始调用次数的商,并显示为 cumtime 之后的 percall)。特定计时指标可能因工具而异;然而,在一般情况下,我们可以预期在类似工具中会获得与 cProfile 的计时指标相似的选择。 调用(频率)剖析时要考虑的另一个指标是对方法进行的调用次数。例如,cProfile 会突出显示函数调用的次数以及其中有多少是原生调用。然而,如果一个方法的速度可以接受,那么它被调用的频率非常高,以至于它成为一个巨大的时间消耗。我们希望从剖析器中了解这一点。 理解方法和行剖析一般来说,大多数剖析教程都基于跟踪方法计时指标,以便初学者理解剖析。 顾名思义,行剖析是指逐行剖析 Python 代码。我们可以将其视为方法剖析;然而,它更细粒度。行剖析最常用的指标是计时指标。作为初学者,也建议先从剖析方法开始,在开始熟悉它们之后再继续。 理解 Python cProfile 和 profile 模块cProfile 和 profile 模块在 Python 3 版本中可用。这些模块生成的数字可以使用 pstats 模块格式化为报告。 让我们看一个 cProfile 模块的以下示例,它显示了一个脚本的数字。 示例 输出 253 function calls (246 primitive calls) in 0.002 seconds Ordered by: standard name ncalls tottime percall cumtime percall filename:lineno(function) 1 0.001 0.001 0.001 0.001 说明 在上面的示例中,我们导入了 cProfile 和 re 模块。然后我们使用了 cProfile 模块的 run() 函数。我们在该函数中指定了 re 模块的 compile() 函数。一旦我们执行以上操作,我们就可以在此 cProfile 示例中观察到“按速度(时间)剖析”下涵盖的不同时间指标(如 ncalls 和 tottime)。profile 模块提供类似的输出和类似的命令。通常,如果 cProfile 不可用,我们会切换到 profile。 APM 选项大多数 APM 工具都是功能齐全的监控工具。它们通常提供行和方法剖析。计时指标是这些工具中的一等公民。我们不会在此列出工具,因为几乎所有工具都具有这些功能。 理解内存剖析内存使用量是我们可以剖析的另一个常见组件。内存剖析的目的是查找内存泄漏并优化 Python 程序的内存使用。对于通用的 Python 选项,Python 3 最推荐的内存剖析工具是 pympler 和 objgraph 库。 理解 Python Pympler 库Python Pympler 库的官方文档提供了更多详细信息。我们可以通过多种方式使用 Pympler 库,如下所示:
让我们看一个用于跟踪类对象生命周期的以下示例。 示例 输出 ---- SUMMARY ------------------------------------------------------------------ Random Numbers - Begin active 0 B average pct RandomNumbers 0 192 B 0 B 0% End active 0 B average pct RandomNumbers 3 11.44 MB 3.81 MB 0% ------------------------------------------------------------------------------- 说明 在上面的代码片段中,我们从 pympler 和 NumPy 库导入了 classtracker 模块。然后我们创建了一个名为 RandomNumbers 的类,在其中我们定义了一个使用 randint() 函数生成随机数的函数。我们创建了一个 ClassTracker() 类的对象,并使用 track_class() 函数来跟踪 RandomNumbers 类的对象。然后我们使用 create_snapshot() 函数收集当前的每个实例统计信息,并保存与 Python 进程相关的内存总量。我们实例化了 RandomNumbers 类,并再次使用了 create_snapshot() 函数。最后,我们使用 stats.print_summary() 函数打印摘要。 理解 Python objgraph 库根据 objgraph 库的创建者所说,该库的目的是帮助查找内存泄漏。正如 Marius Gedminas 所说:“目标是选择内存中不应该存在的对象,然后查看是什么引用使其保持活动状态。” 我们可以说 Marius 强调了 objgraph 库的视觉化比其他内存剖析工具更好,这是它的优势。Marius 曾举例说明 objgraph 库在查找内存泄漏方面的作用;然而,由于篇幅限制,我们在此不作介绍。 APM 选项没有适用于内存剖析的 APM 工具。 理解确定性剖析和统计剖析之间的区别进行剖析时,意味着我们需要监控执行情况。这可能会影响被监控的底层软件。要么我们监控所有函数调用和异常事件,要么我们使用随机采样并推导出数字。前者称为确定性剖析,后者称为统计剖析。当然,每种方法都有其优点和缺点。确定性剖析可以非常精确;然而,其额外的开销可能会影响其准确性。统计剖析的开销较小,缺点是精度较低。 我们之前介绍的 cProfile 库使用了确定性剖析。让我们来看另一个使用统计剖析的开源 Python 剖析器。这个剖析器被称为 pyinstrument 库。 理解 Python pyinstrument 库pyinstrument 库在两个方面与其他人通常的剖析器区别开来。首先,它强调它使用统计剖析而不是确定性剖析。它认为,虽然确定性剖析可以提供比统计剖析更高的精度,但额外的精度需要更多的开销。额外的开销可能会影响准确性,并导致优化程序的不正确部分。特别是,它指出使用确定性剖析意味着“大量调用 Python 函数的代码会频繁调用剖析器,使其运行更慢。”这就是结果失真的方式,程序的不正确部分得到优化。 其次,pyinstrument 库通过“全栈记录”来区分自己。让我们用 cProfile 库进行比较。cProfile 库通常会测量一个函数列表,然后按每个函数花费的时间对其进行排序。相比之下,pyinstrument 库的设计方式是它会跟踪,例如,在 Web 请求期间调用每个函数的原因——因此,具有全栈记录功能。这使得该库非常适合流行的 Web 框架,如基于 Python 的 Django 和 Flask。而全栈记录正是我们将在本教程中介绍的最后一个概念。 理解全栈记录市场上所有不同的 APM 工具都可以被视为全栈记录功能。全栈记录背后的概念是,当一个请求通过堆栈的每一层时,我们需要看到性能瓶颈出现在堆栈的哪一层。有时缓慢可能发生在 Python 脚本之外。 现在,让我们了解一下我们可用的著名的 APM 选项。 APM 选项我们可以将 APM 选项分为两类:
对于 Python 特定开源 APM,我们可以选择 Elastic APM。Python 特定托管 APM 选项的例子有 New Relic、Scout 和 AppDynamics。托管 APM 选项与 Stackify 拥有的 Retrace 非常相似。然而,Retrace 是一个一站式服务,取代了一些其他工具,并且只按使用量收费。除了剖析应用程序代码之外,这些工具还有助于我们跟踪 Web 请求。我们可以通过技术堆栈(包括数据库查询和 Web 服务器的请求)来理解 Web 请求所消耗的挂钟时间。这使得这些选项成为重要的剖析工具,如果我们有 Web 或分布式应用程序的话。 奖励部分:剖析查看器万一有人感到困惑,剖析查看器不是剖析器。然而,它们可以支持将剖析统计信息转换为更具视觉吸引力的显示。一个例子是 SnakeViz,一个用于 Python cProfile 模块输出的基于浏览器的图形查看器。此外,SnakeViz 提供了一个旭日图。 另一个更好地显示 cProfile 统计信息的选项是 tuna。Tuna 允许我们处理运行时和导入剖析,它利用 D3 和 Bootstrap 作为显示的基础技术。 |
我们请求您订阅我们的新闻通讯以获取最新更新。