Python 如何逐行分析cython函数

Python 如何逐行分析cython函数,python,profiling,cython,Python,Profiling,Cython,我经常努力在我的cython代码中找到瓶颈。如何逐行分析cython函数?Robert Bradshaw帮助我获得了Robert Kern的line\u profiler工具,该工具用于cdef函数,我想我应该在stackoverflow上分享结果 简而言之,设置一个常规的.pyx文件和构建脚本,并在调用cythonize之前添加以下内容 # Thanks to @tryptofame for proposing an updated snippet from Cython.Compiler.O

我经常努力在我的
cython
代码中找到瓶颈。如何逐行分析
cython
函数?

Robert Bradshaw帮助我获得了Robert Kern的
line\u profiler
工具,该工具用于
cdef
函数,我想我应该在
stackoverflow
上分享结果

简而言之,设置一个常规的
.pyx
文件和构建脚本,并在调用
cythonize
之前添加以下内容

# Thanks to @tryptofame for proposing an updated snippet
from Cython.Compiler.Options import get_directive_defaults
directive_defaults = get_directive_defaults()

directive_defaults['linetrace'] = True
directive_defaults['binding'] = True
此外,您需要通过修改
扩展设置来定义C宏
CYTHON_TRACE=1
,以便

extensions = [
    Extension("test", ["test.pyx"], define_macros=[('CYTHON_TRACE', '1')])
]
iPython
笔记本中使用
%%cython
魔术的工作示例如下:

虽然我不会真正称之为评测,但还有另一种方法可以通过运行
Cython
-a
(注释)来分析Cython代码,这将创建一个突出显示主要瓶颈的网页。例如,当我忘记声明一些变量时:

正确声明后(
cdef-double-dudz,dvdz
):

虽然展示了使用
setup.py
-方法分析Cython代码的方法,但这个答案是关于IPython/Jupiter笔记本中的特殊分析,或多或少是对IPython/Jupiter的“翻译”

%prun
-魔法:

如果应该使用,则将Cython的编译器指令
profile
设置为
True
(以下是Cython文档中的示例):

使用全局指令(即
#cython:profile=True
)比修改全局cython状态更好,因为更改它将导致重新编译扩展(如果全局cython状态更改,则情况并非如此-使用旧全局状态编译的旧缓存版本将被重新加载/重用)

现在呢

%prun -s cumulative approx_pi(1000000)
收益率:

        1000005 function calls in 1.860 seconds

   Ordered by: cumulative time

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.000    0.000    1.860    1.860 {built-in method builtins.exec}
        1    0.000    0.000    1.860    1.860 <string>:1(<module>)
        1    0.000    0.000    1.860    1.860 {_cython_magic_404d18ea6452e5ffa4c993f6a6e15b22.approx_pi}
        1    0.612    0.612    1.860    1.860 _cython_magic_404d18ea6452e5ffa4c993f6a6e15b22.pyx:7(approx_pi)
  1000000    1.248    0.000    1.248    0.000 _cython_magic_404d18ea6452e5ffa4c993f6a6e15b22.pyx:4(recip_square)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
linetrace=True
触发在生成的C代码中创建跟踪,并暗示
profile=True
,因此不能另外设置。如果没有
binding=True
line\u profiler没有必要的代码信息,并且需要
CYTHON\u TRACE\u NOGIL=1
,因此在使用C编译器编译时也会激活行分析(而不是被C预处理器丢弃)。如果不应在每行的基础上分析nogil块,也可以使用
CYTHON_TRACE=1

现在,它可以如下使用,传递函数,这些函数应该通过
-f
选项进行行分析(使用
%lprun?
获取有关可能选项的信息):

这将产生:

Timer unit: 1e-06 s

Total time: 1.9098 s
File: /XXXX.pyx
Function: recip_square at line 5

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     5                                           def recip_square(i):
     6   1000000    1909802.0      1.9    100.0      return 1. / i ** 2

Total time: 6.54676 s
File: /XXXX.pyx
Function: approx_pi at line 8

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     8                                           def approx_pi(n=10000000):
     9         1          3.0      3.0      0.0      val = 0.
    10   1000001    1155778.0      1.2     17.7      for k in range(1, n + 1):
    11   1000000    5390972.0      5.4     82.3          val += recip_square(k)
    12         1          9.0      9.0      0.0      return (6 * val) ** .5
然而,
line_profiler'与
cpdef`-功能有一个小问题:它无法正确检测功能体,显示了一种可能的解决方法


应该知道,与“正常”运行相比,分析(所有在线分析)改变了执行时间及其分布。在这里,我们看到,对于同一个函数,根据分析的类型,需要不同的时间:

Method (N=10^6):        Running Time:       Build with:
%timeit                 1 second
%prun                   2 seconds           profile=True
%lprun                  6.5 seconds         linetrace=True,binding=True,CYTHON_TRACE_NOGIL=1

cython调试器允许您暂停它吗?当然,不输入变量会降低代码的速度。但是
-a
不会给你任何关于实际运行时的信息,只会告诉你是否正在进行
python
调用。但是在我的例子中,像在将python移植到Cython代码时忘记声明变量这样的事情通常会让代码变慢,这是一种快速而简单的测试这些事情的方法。这就是为什么我称之为“不是真正的剖析”;这只是简单的第一次代码检查/分析。非常有用,谢谢。一个细节:我发现line_profiler声明所分析的文件是原始的.pyx文件。我很确定我所有的东西都指向了.pyd,所以我怀疑探查器只是从.pyd中读取内容进行显示,而仍然从编译版本中获取实际时间。有人在不使用笔记本的情况下尝试过这个解决方案吗?我试过了,但它只是忽略了cythonized代码。另外,如果我试图用
@profile
来装饰函数,我无法编译文件,因为distils返回
未声明的名称未内置:profile
请注意,在最近的版本中发生了更改:@%%cython magic在iPython笔记本中使用以下代码,因为“from cython.Compiler.Options import directive\u defaults”是不推荐的导入Cython指令\u defaults=Cython.Compiler.Options.get\u directive\u defaults()指令\u defaults=Cython.Compiler.Options.get\u directive\u defaults(),因为“from Cython.Compiler.Options导入指令\u defaults”似乎不推荐
%load_ext line_profiler
%lprun -f approx_pi -f recip_square approx_pi(1000000)
Timer unit: 1e-06 s

Total time: 1.9098 s
File: /XXXX.pyx
Function: recip_square at line 5

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     5                                           def recip_square(i):
     6   1000000    1909802.0      1.9    100.0      return 1. / i ** 2

Total time: 6.54676 s
File: /XXXX.pyx
Function: approx_pi at line 8

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
     8                                           def approx_pi(n=10000000):
     9         1          3.0      3.0      0.0      val = 0.
    10   1000001    1155778.0      1.2     17.7      for k in range(1, n + 1):
    11   1000000    5390972.0      5.4     82.3          val += recip_square(k)
    12         1          9.0      9.0      0.0      return (6 * val) ** .5
Method (N=10^6):        Running Time:       Build with:
%timeit                 1 second
%prun                   2 seconds           profile=True
%lprun                  6.5 seconds         linetrace=True,binding=True,CYTHON_TRACE_NOGIL=1