在Jupyter Notebook中使用Cython进行代码行性能分析

4

我正在尝试在Jupyter notebook中使用liner_profiler库来分析Cython函数,但只有一半起作用。我只得到了函数的第一行,并没有分析结果。

%%cython -a
# cython: linetrace=True
# cython: binding=True
# distutils: define_macros=CYTHON_TRACE_NOGIL=1
import numpy as np
cimport numpy as np
from datetime import datetime
import math


cpdef np.int64_t get_days(np.int64_t year, np.int64_t month):
    cdef np.ndarray months=np.array([31,28,31,30,31,30,31,31,30,31,30,31])
    if month==2:
        if (year%4==0 and year%100!=0) or (year%400==0):
            return 29
    return months[month-1]

性能剖析结果仅显示一行代码

    Timer unit: 1e-07 s

Total time: 0.0015096 s
File: .ipython\cython\_cython_magic_0154a9feed9bbd6e4f23e57d73acf50f.pyx
Function: get_days at line 15

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    15                                           cpdef np.int64_t get_days(np.int64_t year, np.int64_t month):

你是否真的调用了这个函数?看起来你只是定义了这个函数,因此只有 cpdef 行被“运行”。 - DavidW
是的,我将在下一个单元格中调用它:%lprun -f get_days get_days(2019,3) - Alex T
线性分析器似乎在处理cdef函数(或cpdef)时存在问题。不确定这是Cython还是line_profiler的问题。 - ead
这也被跟踪为问题(https://github.com/pyutils/line_profiler/issues/13)。 - jakirkham
1个回答

5
这可能被视为line_profiler中的一个错误(如果它应该支持Cython的话)。为了获取受监测函数的代码,line_profiler阅读pyx文件,并试图通过使用inspect.getblock来提取代码:
...
# read pyx-file
all_lines = linecache.getlines(filename)
# try to extract body of the function strarting at start_lineno:
sublines = inspect.getblock(all_lines[start_lineno-1:])
...

然而, getblock 对于 cpdef 函数一无所知,因为 Python 只有 def 函数,从而得到错误的函数体(即只有签名)。

解决方法:

一个简单的解决方法是引入一个虚拟的 def 函数,它可以成为 cpdef 函数的哨兵。这样,inspect.getblock 将返回整个 cpdef 函数的主体和哨兵函数的主体,即:

%%cython
...
cpdef np.int64_t get_days(np.int64_t year, np.int64_t month):
    ...

def get_days_sentinel():
    pass

现在,报告%lprun -f get_days get_days(2019,3)如下:

Timer unit: 1e-06 s

Total time: 1.7e-05 s
File: XXXX.pyx
Function: get_days at line 10

Line #      Hits         Time  Per Hit   % Time  Line Contents
==============================================================
    10                                           cpdef np.int64_t get_days(np.int64_t year, np.int64_t month):
    11         1         14.0     14.0     82.4      cdef np.ndarray months=np.array([31,28,31,30,31,30,31,31,30,31,30,31])
    12         1          1.0      1.0      5.9      if month==2:
    13                                                   if (year%4==0 and year%100!=0) or (year%400==0):
    14                                                       return 29
    15         1          2.0      2.0     11.8      return months[month-1]
    16                                           
    17                                           def get_days_sentinel():
    18                                               pass

哨兵仍然会留下一些难看的尾行,但与看不到任何东西相比,这可能更好。


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