如何使用cProfile来分析Python代码

Python可能不是最快的语言,但是它通常足够快。 当程序员的时间比CPU时间更重要时,Python是理想的选择。

就是说,如果给定的Python应用程序很落后,那么您就没有义务仅仅吸收它。 Python解释器的基本安装附带的工具可以为您提供有关程序哪些部分运行缓慢的详细反馈,并提供有关如何加快它们的一些提示。

[InfoWorld的要点: 开始使用Anaconda,这是数据科学的Python发行版 Python的Anaconda发行版中的新增功能 5个用于数据科学的基本Python工具-现已改进 | 通过InfoWorld的App Dev Report新闻通讯了解编程方面的热门话题。 ]

如何使用cProfile

cProfile模块收集有关Python程序执行时间的统计信息。 它可以报告从整个应用程序到单个语句或表达式的所有内容。

这是一个如何使用cProfile的玩具示例:

def add(x,y):
    x+=str(y)
    return x

def add_2(x,y):
    if y % 20000 == 0:
        z=[]
        for q in range(0,400000):
            z.append(q)

def main():
    a=[]
    for n in range(0,200000):
        add(a,n)
        add_2(a,n)
        
if __name__ == '__main__':
    import cProfile
    cProfile.run('main()')

此示例运行应用程序的main()函数,并分析main()main()调用的所有内容的性能。 也可以仅分析程序的一部分 ,但是对于初学者来说,最常见的用途是分析整个程序。

运行上面的示例,您将收到类似以下输出的内容:

如何使用cProfile来分析Python代码IDG

此处显示的是程序进行的所有函数调用的列表,以及有关每个函数的统计信息:

  • 在顶部(蓝色的第一行)中,我们看到了配置文件程序中的调用总数和总执行时间。 您可能还会看到“原始调用”的数字,表示非递归调用,或直接对函数进行的调用,这些调用又不会在调用堆栈中进一步调用自身。
  • ncalls :拨打的电话数。 如果看到两个数字用斜杠分隔,则第二个数字是该函数的原始调用数。
  • tottime :在该函数中花费的总时间, 包括对其他函数的调用。
  • percall:每次通话的平均时间为tottime,通过采取tottimencalls除以得到。
  • cumtime :在该函数上花费的总时间,包括对其他函数的调用。
  • percall (#2):每次通话的平均时间为cumtimecumtime除以ncalls )。
  • filename:lineno:相关调用的文件名,行号和函数名。

如何修改cProfile报告

默认情况下, cProfile按“标准名称”对输出进行排序,这意味着它按最右列中的文本(文件名,行号等)进行排序。

如果希望每个功能调用的自上而下的常规报告供参考,则默认格式很有用。 但是,如果您试图解决瓶颈问题,则可能需要首先列出程序中最耗时的部分。

我们可以通过稍微不同地调用cProfile来产生这些结果。 请注意,如何重新ncalls上述程序的底部,以按不同的列对统计信息进行排序(在本例中为ncalls ):

if __name__ == '__main__':
    import cProfile, pstats
    profiler = cProfile.Profile()
    profiler.enable()
    main()
    profiler.disable()
    stats = pstats.Stats(profiler).sort_stats('ncalls')
    stats.print_stats()

结果将如下所示:

如何使用cProfile来分析Python代码IDG

这是所有工作的原理:

  • 我们不是通过不太灵活的cProfile.run()执行命令,而是创建了一个分析对象 profiler
  • 当我们想要分析某些动作时,我们首先在事件探查器对象实例上调用.enable() ,然后运行该动作,然后调用.disable() (这是仅分析程序一部分的一种方法。)
  • pstats模块用于处理探查器对象收集的结果并打印这些结果。

将事件探查器对象和pstats结合使用,使我们能够处理捕获的概要文件数据-例如,对生成的统计数据进行不同的排序。 在此示例中,使用.sort_stats('ncalls')通过ncalls列对统计信息进行排序。 其他排序选项可用。

如何使用cProfile结果进行优化

cProfile输出可用的排序选项使我们可以找出程序中潜在的性能瓶颈。

ncalls

您可以使用cProfile挖掘的第一个也是最重要的信息是,通过ncalls列可以最频繁地调用哪些函数。

在Python中,仅进行函数调用的行为会导致相对大量的开销。 如果某个函数在紧密循环中反复调用,即使它不是长时间运行的函数,也可以保证会影响性能。

在上面的示例中,函数add (和函数add_2 )被循环调用。 将循环移到add函数本身中,或完全内联add函数将解决此问题。

tottime

另一个有用的统计细节,可以通过tottime列来花费程序的大部分时间执行功能。

在上面的示例中, add_2函数使用循环来模拟一些昂贵的计算,这会将其tottime得分推高了。 具有较高tottime得分的任何函数都应仔细观察,尤其是在多次调用或紧密循环调用时。

请注意,您始终需要考虑使用该函数的上下文 如果某个函数的使用时间tottime但是仅被调用一次(例如,仅在程序启动时),则它不太可能成为瓶颈。 但是,如果您要减少启动时间,则需要知道启动时调用的函数是否正在使其他所有内容等待。

如何导出cProfile数据

如果要以更高级的方式使用cProfile生成的统计信息,可以将其导出到数据文件中:

stats = pstats.Stats(profiler)
stats.dump_stats('/path/to/stats_file.dat')

此文件可在使用读回pstats模块,然后进行排序或显示pstats 数据也可以被其他程序重复使用。 两个例子:

  • pyprof2calltree从配置文件数据呈现程序的调用图和使用情况统计的详细可视化。 本文提供了其用法的详细实际示例。
  • snakeviz还从cProfile数据生成可视化cProfile ,但对数据使用不同的表示形式-“旭日”而不是pyprof2calltree的“ flame”图。

超越cProfile进行Python分析

cProfile几乎不是剖析Python应用程序的唯一方法。 cProfile与Python捆绑在一起,无疑是最方便的方法之一。 但其他人值得关注。

一个项目py-spy通过采样其调用活动来构建Python应用程序的配置文件。 py-spy可用于检查正在运行的Python应用程序,而无需停止和重新启动它,而无需更改其代码库,因此可用于分析已部署的应用程序。 py-spy还会生成一些有关Python运行时产生的开销(例如,垃圾回收开销)的统计信息,而cProfile不会。

From: https://www.infoworld.com/article/3329750/how-to-profile-python-code.html