天天看点

Python:使用cProfile对代码段进行性能分析

作者:趣学Python
Python:使用cProfile对代码段进行性能分析

当尝试优化缓慢的函数或模块时,通过性能分析可以更好地了解代码的性能瓶颈。在Python中,可以使用cProfile模块对代码进行快速的性能分析。下面是两个不同版本的代码片段,可用于对代码段进行性能分析。这些代码片段改编自cProfile文档中的“Profile”示例。多年来,我一直使用这些代码片段来缩小性能问题的范围。

基本版本

此版本适用于对模块级别代码进行性能分析。将以下两个代码块粘贴到目标代码周围:

import cProfile, pstats

profiler = cProfile.Profile()
profiler.enable()

...

profiler.disable()
pstats.Stats(profiler).sort_stats(pstats.SortKey.CUMULATIVE).print_stats(100)
           

第一个代码块创建并启动性能分析器。第二个代码块停止它并打印前100个函数,按累积调用时间排序。

例如,对一些类定义进行性能分析:

from django.db import models

import cProfile, io, pstats

profiler = cProfile.Profile()
profiler.enable()


class DanceKind(models.IntegerChoices):
    FANDANGO = 1
    TANGO = 2


class Dance(models.Model):
    kind = models.IntegerField(choices=DanceKind.choices)


profiler.disable()
out = io.StringIO()
pstats.Stats(profiler).sort_stats(pstats.SortKey.CUMULATIVE).print_stats(100)
           

当模块导入时,您将看到来自print_stats()调用的输出:

$ python manage.py shell
      18305 function calls (17540 primitive calls) in 0.044 seconds

Ordered by: cumulative time
List reduced from 552 to 100 due to restriction <100>

ncalls ... cumtime  percall filename:lineno(function)
  97/2 ...   0.044    0.022 {built-in method builtins.__build_class__}
     1 ...   0.043    0.043 /.../django/db/models/base.py:95(__new__)
     6 ...   0.043    0.007 /.../django/db/models/base.py:369(add_to_class)
     1 ...   0.043    0.043 /.../django/db/models/options.py:175(contribute_to_class)
     1 ...   0.043    0.043 /.../django/utils/connection.py:14(__getattr__)
     1 ...   0.043    0.043 /.../django/utils/connection.py:56(__getitem__)
     1 ...   0.043    0.043 /.../django/db/utils.py:191(create_connection)
     1 ...   0.043    0.043 /.../django/db/utils.py:103(load_backend)
     1 ...   0.043    0.043 /.../importlib/__init__.py:108(import_module)
   2/1 ...   0.043    0.043 <frozen importlib._bootstrap>:1192(_gcd_import)
...
           

表格显示了函数的调用情况,以文件名、行号和名称为标识。它们按累积时间(“cumtime”)排序,即在该函数中花费的总时间(包括其被调用方),按最长时间优先排序。

请注意,如果两个代码块之间的代码段引发异常,则性能分析器将继续运行。通常不用担心这个问题,因为模块级别的异常通常会停止程序。

以下是已润色的文章:

上下文管理器和装饰器版本

这个版本适用于对函数或函数部分进行分析。通过使用 @contextlib.contextmanager​,它既可以用作上下文管理器,也可以用作装饰器。

首先,将以下定义粘贴到目标模块中:

from contextlib import contextmanager


@contextmanager
def profile_and_print():
    import cProfile, pstats

    profiler = cProfile.Profile()
    profiler.enable()

    try:
        yield
    finally:
        profiler.disable()

    pstats.Stats(profiler).sort_stats(pstats.SortKey.CUMULATIVE).print_stats(100)
           

通过使用 try/finally​,如果发生异常,分析器将始终处于禁用状态。只有在成功时才会打印配置文件,尽管您可以通过缩进最后一行来更改此设置。

要将其用作函数装饰器:

from django.shortcuts import render

from example.models import Dance

...

@profile_and_print()
def index(request):
    context = {
        "top_dancers": get_top_dancers(),
        "total_dances": Dance.objects.count(),
    }
    return render(request, "index.html", context)
           

以及作为上下文管理器:

def index(request):
    context = {
        "top_dancers": get_top_dancers(),
        "total_dances": Dance.objects.count(),
    }
    with profile_and_print():
        return render(request, "index.html", context)
           

在任一情况下,运行分析代码块将再次为该部分打印分析结果:

4092 function calls (4012 primitive calls) in 0.008 seconds

Ordered by: cumulative time
List reduced from 376 to 100 due to restriction <100>

ncalls ... cumtime  percall filename:lineno(function)
     1 ...   0.008    0.008 /.../django/shortcuts.py:17(render)
     1 ...   0.007    0.007 /.../django/template/loader.py:52(render_to_string)
     1 ...   0.006    0.006 /.../django/template/loader.py:5(get_template)
...
           

可能的调整

以下是您可能想要修改此段代码的几种方式。

排序

要按其他指标排序,请使用从 此表 中选择 SortKey.CUMULATIVE​ 之外的其他值。最有用的是:

  • ​SortKey.TIME​,用于大多数内部时间,即在函数内部花费的时间,不包括对其他函数的调用。最慢的函数通常是优化的易发现目标。
  • ​SortKey.CALLS​,用于调用次数。可能有方法可以消除重复的函数调用,例如使用缓存。

显示

默认情况下,Stats​ 类将输出到标准输出。要输出到标准错误,请在创建时传递 stream=sys.stderr​:

pstats.Stats(profiler, stream=sys.stderr).sort_stats(
    pstats.SortKey.CUMULATIVE,
).print_stats(100)
           

要保存到文件,请将 print_stats()​ 替换为 dump_stats()​:

pstats.Stats(profiler).sort_stats(
    pstats.SortKey.CUMULATIVE,
).dump_stats("profile.stats")
           

您可以使用可视化工具(如 tuna)使用此文件。

总结

在Python中,使用 cProfile 模块可以快速进行性能分析,以了解代码的性能问题。这篇文章提供了两个版本的代码片段,一个适用于对模块级别代码进行性能分析,另一个适用于对函数或函数部分进行性能分析。您可以按照代码片段的指示将其粘贴到目标代码周围,然后运行并查看分析结果。分析结果将按照累积调用时间排序,并且可以根据需要进行排序和显示。性能分析器将始终处于禁用状态,只有在成功时才会打印配置文件。