Search code examples
jupyter-notebookcythonline-profiler

Line profiling with cython in jupyter notebook


I'm trying to use liner_profiler library in jupyter notebook with cython function. It is working only halfway. The result I get only consist of first row of the function and no profiling results.

%%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]

For the profiling result int onlt shows one line of code

    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):

Solution

  • This can be seen as a bug in the line_profiler (if it is supposed to support Cython). To get the code of the profiled function, line_profiler reads the pyx-file and tries to extract the code with help of 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:])
    ...
    

    However, getblock knows nothing about cpdef-function, as python has only def-functions and thus yields wrong function-body (i.e. only the signature).

    Workaround:

    A simple work around would be to introduce a dummy def-function, which would be a sentinel for the cpdef-function in such a way, that inspect.getblock would yield the whole body of the cpdef-function + body of the the sentinel function, i.e.:

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

    and now the report %lprun -f get_days get_days(2019,3) looks as follows:

    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
    

    There are still somewhat ugly trailing lines from the sentinel, but it is probably better as not seeing anything at all.