分析Python C扩展库的性能特征

48

我已经开发了一个Python C扩展,从Python中接收数据并计算一些CPU密集型的计算。

可以对C扩展进行性能分析吗?

问题在于,编写一个用于性能分析的C语言示例测试可能很具有挑战性,因为代码依赖于特定的输入和数据结构(由Python控制代码生成)。

你有什么建议吗?

5个回答

31

在 pygabriel 的评论之后,我决定上传一个使用来自 google-perftools 的 cpu-profiler 实现 python 扩展程序的分析器包到 pypi 上: http://pypi.python.org/pypi/yep


谢谢,我发现这真的很有用,而且很容易上手! - robince
这很棒,而且非常易于使用。谢谢。 - rd11
如何在Windows上使用它? - Mudit Jain

22

我已经找到了解决方法,使用google-perftools。窍门是用Python包装StartProfiler和StopProfiler函数(在我的情况下通过Cython)。

要对C扩展进行分析,只需将Python代码包装在StartProfiler和StopProfiler调用中即可。

from google_perftools_wrapped import StartProfiler, StopProfiler
import c_extension # extension to profile c_extension.so

StartProfiler("output.prof")
... calling the interesting functions from the C extension module ...
StopProfiler()

例如,要进行分析,您可以以callgrind格式导出,然后在kcachegrind中查看结果:

pprof --callgrind c_extension.so output.prof > output.callgrind 
kcachegrind output.callgrind

非常感谢您的提示!!我实际上也在寻找同样的东西。我会尝试的。 - ThR37
编辑:确实完美运行!即使在 CPU 分析期间有时会出现段错误,但使用 ctypes 的简单包装器也可以(但这在文档中是“正常的”并得到了解释...我正在使用 x86_64 :( ) - ThR37
非常感谢这个小贡献。它非常有用 :-) 我所看到的是,pprof(或者说,在Debian软件包中的google-pprof),在分析相同代码时,我没有得到像使用valgrind一样多的符号解缠。我需要在编译时指定-pg吗? - Miquel Ramirez
链接已经失效。我在这里找到了一个可用的链接:https://github.com/google/pprof - MaBekitsur
这种方式能进行堆分析吗? - theFrok

7
我发现使用py-spy非常容易。请参考这篇博客文章,了解它的本地扩展支持的解释。 亮点:
  • 可通过pip安装
  • 基于CPU采样
  • 无需编译器标志
  • 执行您的程序或附加到运行中的进程
  • 多个输出格式(我建议使用--format speedscope
  • 可配置采样率

2
这应该是最佳答案! - Labo

5

我的同事告诉我使用ltrace(1),在相同的情况下帮助了我很多。

假设你的C扩展共享对象名称为myext.so,并且你想执行benchmark.py,那么:

ltrace -x @myext.so -c python benchmark.py

它的输出类似于:
% time     seconds  usecs/call     calls      function
------ ----------- ----------- --------- --------------------
 24.88   30.202126     7550531         4 ldap_result
 12.46   15.117625     7558812         2 l_ldap_result4
 12.41   15.059652     5019884         3 ldap_chase_v3referrals
 12.41   15.057678     3764419         4 ldap_new_connection
 12.40   15.050310     3762577         4 ldap_int_open_connection
 12.39   15.042360     3008472         5 ldap_send_server_request
 12.38   15.029055     3757263         4 ldap_connect_to_host
  0.05    0.057890       28945         2 ldap_get_option
  0.04    0.052182       26091         2 ldap_sasl_bind
  0.03    0.030760       30760         1 l_ldap_get_option
  0.03    0.030635       30635         1 LDAP_get_option
  0.02    0.029960       14980         2 ldap_initialize
  0.02    0.027988       27988         1 ldap_int_initialize
  0.02    0.026722       26722         1 l_ldap_simple_bind
  0.02    0.026386       13193         2 ldap_send_initial_request
  0.02    0.025810       12905         2 ldap_int_select
....

如果您的共享对象文件名中包含-+字符,则需要特别注意。这些字符不会被视为正常字符(详见man 1 ltrace)。

通配符*可以作为解决方法,例如将-x @myext-2.so替换为-x @myext*


4

使用 gprof,您可以对任何已经 正确编译和链接(例如,在 gprof 中使用的 gcc -pg)的程序进行分析。如果您使用的是未使用 gcc 编译的 Python 版本(例如,PSF 发布的预编译 Windows 版本),您需要研究该平台和工具链中存在哪些等效工具(在 Windows PSF 的情况下,也许 mingw 可以帮助)。可能会有“无关”数据(Python 运行时的内部 C 函数),如果有,则 gprof 显示的百分比可能不适用,但是调用次数和持续时间的绝对数字仍然有效,并且您可以后处理 gprof 的输出(例如,使用一个小 Python 脚本;-) 来排除无关数据并计算所需的百分比。


1
我仍然在使用中遇到一些问题,但可能只是我的错。 在编译和链接(gcc)Python源代码后,可执行文件正确生成gmon.out文件。 如果我执行加载已使用-pg标志编译的C扩展(*.so)的脚本,则分析输出(gprof /path/custom/python,也不是gprof extension.so)不显示包含在C库中的函数调用。 我有什么遗漏的吗? - pygabriel
2
gprof与dlopen()不兼容,可能是因为它在库加载之前初始化其内存映射。仅使用-pg标志编译对于帮助gprof没有任何作用,如果主机可执行文件(在本例中为python)未直接链接到.so文件。 - Justin W

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接