Python是否有统计分析器?如果没有,我该如何编写一个?

20 投票
8 回答
3912 浏览
提问于 2025-04-16 15:28

我需要运行一个Python脚本,随机运行一段时间,然后暂停它,获取一个堆栈跟踪信息,再继续运行。为了实现这个,我在网上查了很多资料,但没有找到明显的解决办法。

8 个回答

3

Python已经具备了你所需要的所有功能,不用去改动解释器。

你只需要使用traceback模块,配合sys._current_frames()这个函数。你只需要找到一种方法,按照你想要的频率来输出需要的追踪信息,比如可以使用UNIX信号,或者用另一个线程。

如果你想快速开始你的代码,可以参考这个提交中的做法:

  1. 从那个提交中复制threads.py模块,或者至少复制堆栈追踪输出的函数(ZPL许可证,非常宽松):

  2. 把它连接到一个信号处理器,比如SIGUSR1

然后你只需要运行你的代码,并根据需要频繁地用SIGUSR1来“杀死”它。

如果你想用相同的技术,偶尔对单个线程中的单个函数进行“采样”,可以使用另一个线程来计时,我建议你研究一下Products.LongRequestLogger的代码和测试(这是我在Nexedi工作时开发的):

无论这是否算是“统计”分析,Mike Dunlavey的回答被intuited引用,提出了一个很有说服力的观点,认为这是一种非常强大的“性能调试”技术,我个人也有经验,确实能帮助快速找到性能问题的真正原因。

5

我想到几种方法可以做到这一点:

  • 与其在程序运行时尝试获取堆栈跟踪,不如直接给它发个中断,然后解析输出。你可以用一个shell脚本,或者用另一个Python脚本来调用你的应用程序作为子进程。基本的想法在这个针对C++问题的回答中有详细解释和支持。

    • 其实,你也可以注册一个后处理例程(使用sys.excepthook),这样可以记录堆栈跟踪,而不需要解析输出。不幸的是,Python没有办法在异常发生的地方继续执行,所以在记录后你不能恢复执行。
  • 如果你想从正在运行的程序中获取堆栈跟踪,你可能需要对实现进行一些修改。所以如果你真的想这样做,可以看看pypy,这是一个主要用Python编写的Python实现。我不知道在pypy中这样做会有多方便。我猜这不会特别方便,因为这涉及到在几乎每条指令中引入一个钩子,这样做效率可能会非常低下。而且,我觉得这与第一种方法相比没有太大优势,除非你需要很长时间才能达到想要开始做堆栈跟踪的状态。

  • 有一套是为gdb调试器设计的,目的是方便调试Python本身。gdb可以连接到一个外部进程(在这种情况下,就是执行你应用程序的Python实例),并且可以对它做几乎任何事情。似乎宏pystack可以让你在当前执行点获取Python堆栈的回溯。我觉得自动化这个过程会很简单,因为你可以(在最坏的情况下)通过expect或其他方式将文本输入到gdb中。

13

这里有一个叫做 statprof 模块

你可以通过 pip install statprof(或者 easy_install statprof)来安装它,安装后就可以使用了:

import statprof

statprof.start()
try:
    my_questionable_function()
finally:
    statprof.stop()
    statprof.display()

关于这个模块,有一些背景信息可以参考 这篇博客

那么,这个模块有什么用呢?其实,Python 已经有两个内置的性能分析工具:lsprof 和已经不再使用的 hotshot。lsprof 的问题在于,它只跟踪函数调用。如果你在某个函数里有几个运行很快的循环,lsprof 对于找出哪些循环真正重要几乎没什么帮助。

几天前,我正好遇到了 lsprof 无法解决的情况:它告诉我有一个运行很快的函数,但这个函数我不熟悉,而且函数的代码比较长,问题所在并不明显。

经过一番在 Twitter 和 Google+ 上的求助,有人推荐了 statprof。不过有个问题:虽然它在进行统计采样(太棒了!),但它只在采样时跟踪函数的第一行(这是什么鬼!?)。所以我对这个问题进行了修复,改进了文档,现在它既好用又不会误导人。以下是它输出的一个例子,更准确地找出了那个运行快的函数中的问题行:

  %   cumulative      self          
 time    seconds   seconds  name    
 68.75      0.14      0.14  scmutil.py:546:revrange
  6.25      0.01      0.01  cmdutil.py:1006:walkchangerevs
  6.25      0.01      0.01  revlog.py:241:__init__
  [...blah blah blah...]
  0.00      0.01      0.00  util.py:237:__get__
---
Sample count: 16
Total time: 0.200000 seconds

我已经把 statprof 上传到 Python 包索引,所以安装起来几乎是小菜一碟:“easy_install statprof”,你就可以开始使用了。

因为 代码在 GitHub 上,欢迎大家提交 bug 报告和改进建议。希望你喜欢!

撰写回答