【问题标题】:Profiling python C extensions分析 python C 扩展
【发布时间】:2011-02-06 14:14:48
【问题描述】:

我开发了一个 python C 扩展,它从 python 接收数据并计算一些 cpu 密集型计算。 可以分析 C 扩展名吗?

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

你有什么建议吗?

【问题讨论】:

    标签: python c profiling


    【解决方案1】:

    我发现py-spy 非常易于使用。有关其原生扩展支持的说明,请参阅 this blog post

    亮点:

    • pip 可安装
    • 基于 CPU 采样
    • 不需要编译器标志
    • 执行您的程序或附加到正在运行的进程
    • 多种输出格式(我推荐--format speedscope
    • 可配置的采样率

    【讨论】:

    • 这应该是最佳答案!
    【解决方案2】:

    我的一位同事告诉我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* 代替-x @myext-2.so

    【讨论】:

      【解决方案3】:

      我找到了使用google-perftools 的方法。诀窍是将函数 StartProfiler 和 StopProfiler 包装在 python 中(在我的例子中是通过 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
      

      【讨论】:

      • 非常感谢您的提示!我实际上在寻找同样的东西。我会试试的。
      • 编辑:确实完美!即使在 CPU 分析期间有时会出现段错误,使用 ctypes 的简单包装器也可以(但这是“正常的”并在文档中进行了解释......我正在使用 x86_64 :()
      • 非常感谢您提供这个小金块。它非常非常有用 :-) 我所看到的是 pprof(或者更确切地说,Debian 软件包中的 google-pprof),我没有像分析时那样得到尽可能多的符号与 valgrind 相同的代码。可能是我在编译时需要指定-pg?
      • 链接已损坏。我在这里找到了一个工作链接:github.com/google/pprof
      • 是否可以通过这种方式进行堆分析?
      【解决方案4】:

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

      【讨论】:

      • 谢谢,我发现这真的很有用,而且很容易上手!
      • 这很棒而且很容易使用。谢谢。
      • 如何在windows上使用?
      【解决方案5】:

      使用gprof,您可以分析任何properly compiled 并链接的程序(gcc -pg 等,在gprof 的情况下)。如果您使用的 Python 版本不是使用 gcc 构建的(例如,PSF 分发的 Windows 预编译版本),您需要研究该平台和工具链存在哪些等效工具(在 Windows PSF 的情况下,可能是 @ 987654326@可以帮忙)。那里可能有“不相关”的数据(Python 运行时中的内部 C 函数),如果是这样,gprof 显示的百分比可能不适用——但绝对数量(调用次数及其持续时间)仍然存在有效,并且您可以对gprof 的输出进行后处理(例如,使用一点 Python 脚本;-)以排除不相关的数据并计算您想要的百分比。

      【讨论】:

      • 我在使用它时仍有一些问题,但也许这只是我的错。在编译和链接(gcc)python 源之后,可执行文件会正确生成 gmon.out 文件。如果我执行加载使用 -pg 标志编译的 C 扩展 ( *.so ) 的脚本,则分析输出 ( gprof /path/custom/python 或 gprof extension.so ) 不会显示 C 中包含的函数调用图书馆。我错过了什么?
      • gprof 不能很好地与 dlopen() 配合使用,可能是因为它在加载库之前初始化了它的内存映射。如果主机可执行文件(在本例中为 python)没有直接链接到 .so 文件,则仅使用 -pg 标志进行编译对 gprof 没有任何帮助。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-01
      • 2019-11-23
      • 1970-01-01
      • 2018-04-10
      相关资源
      最近更新 更多