如何逐行分析 cython 函数
How to profile cython functions line-by-line
我经常努力寻找 cython
代码中的瓶颈。如何逐行分析 cython
函数?
Robert Bradshaw 帮助我获得了 Robert Kern 的 line_profiler
工具,用于 cdef
函数,我想我会在 Whosebug
.
上分享结果
简而言之,设置一个常规 .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
此外,您需要通过修改 extensions
设置来定义 C 宏 CYTHON_TRACE=1
,这样
extensions = [
Extension("test", ["test.pyx"], define_macros=[('CYTHON_TRACE', '1')])
]
在 iPython
笔记本中使用 %%cython
魔法的工作示例如下:
http://nbviewer.ipython.org/gist/tillahoffmann/296501acea231cbdf5e7
虽然我不会真正称之为分析,但还有另一种选择可以通过 运行 cython
和 -a
(注释)分析您的 Cython 代码,这会创建一个网页其中突出了主要瓶颈。例如,当我忘记声明一些变量时:
正确声明它们后 (cdef double dudz, dvdz
):
虽然 shows the way for profiling Cython-code using setup.py
-approach, this answer is about ad-hoc profiling in IPython/Jupiter notebook and is more or less "translation" of Cython-documentation 到 IPython/Jupiter。
%prun
-魔法:
如果应该使用 %prun
-magic,那么将 Cython 的编译器指令 profile
设置为 True
就足够了(这里以 Cython 文档中的示例为例):
%%cython
# cython: profile=True
def recip_square(i):
return 1. / i ** 3
def approx_pi(n=10000000):
val = 0.
for k in range(1, n + 1):
val += recip_square(k)
return (6 * val) ** .5
使用全局指令(即 # cython: profile=True
)比修改全局 Cython 状态更好,因为更改它会导致重新编译扩展(如果全局 Cython 状态是已更改 - 使用旧全局状态编译的旧缓存版本将为 reloaded/reused).
现在
%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}
%lprun
-魔法
如果要使用行分析器(即%lprun
-magic),则应使用不同的指令编译 Cython 模块:
%%cython
# cython: linetrace=True
# cython: binding=True
# distutils: define_macros=CYTHON_TRACE_NOGIL=1
...
linetrace=True
触发在生成的 C 代码中创建跟踪并暗示 profile=True
因此不得另外设置。没有 binding=True
line_profiler 没有必要的代码信息并且需要 CYTHON_TRACE_NOGIL=1
,所以当使用 C 编译器编译时,行分析也会被激活(并且不会被C 预处理器)。如果不应在每行的基础上分析 nogil-blocks,也可以使用 CYTHON_TRACE=1
。
现在可以使用它,例如,传递函数,这些函数应该通过 -f
选项进行行分析(使用 %lprun?
获取有关可能选项的信息):
%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
然而,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
代码中的瓶颈。如何逐行分析 cython
函数?
Robert Bradshaw 帮助我获得了 Robert Kern 的 line_profiler
工具,用于 cdef
函数,我想我会在 Whosebug
.
简而言之,设置一个常规 .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
此外,您需要通过修改 extensions
设置来定义 C 宏 CYTHON_TRACE=1
,这样
extensions = [
Extension("test", ["test.pyx"], define_macros=[('CYTHON_TRACE', '1')])
]
在 iPython
笔记本中使用 %%cython
魔法的工作示例如下:
http://nbviewer.ipython.org/gist/tillahoffmann/296501acea231cbdf5e7
虽然我不会真正称之为分析,但还有另一种选择可以通过 运行 cython
和 -a
(注释)分析您的 Cython 代码,这会创建一个网页其中突出了主要瓶颈。例如,当我忘记声明一些变量时:
正确声明它们后 (cdef double dudz, dvdz
):
虽然 setup.py
-approach, this answer is about ad-hoc profiling in IPython/Jupiter notebook and is more or less "translation" of Cython-documentation 到 IPython/Jupiter。
%prun
-魔法:
如果应该使用 %prun
-magic,那么将 Cython 的编译器指令 profile
设置为 True
就足够了(这里以 Cython 文档中的示例为例):
%%cython
# cython: profile=True
def recip_square(i):
return 1. / i ** 3
def approx_pi(n=10000000):
val = 0.
for k in range(1, n + 1):
val += recip_square(k)
return (6 * val) ** .5
使用全局指令(即 # cython: profile=True
)比修改全局 Cython 状态更好,因为更改它会导致重新编译扩展(如果全局 Cython 状态是已更改 - 使用旧全局状态编译的旧缓存版本将为 reloaded/reused).
现在
%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}
%lprun
-魔法
如果要使用行分析器(即%lprun
-magic),则应使用不同的指令编译 Cython 模块:
%%cython
# cython: linetrace=True
# cython: binding=True
# distutils: define_macros=CYTHON_TRACE_NOGIL=1
...
linetrace=True
触发在生成的 C 代码中创建跟踪并暗示 profile=True
因此不得另外设置。没有 binding=True
line_profiler 没有必要的代码信息并且需要 CYTHON_TRACE_NOGIL=1
,所以当使用 C 编译器编译时,行分析也会被激活(并且不会被C 预处理器)。如果不应在每行的基础上分析 nogil-blocks,也可以使用 CYTHON_TRACE=1
。
现在可以使用它,例如,传递函数,这些函数应该通过 -f
选项进行行分析(使用 %lprun?
获取有关可能选项的信息):
%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
然而,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