Python Line_profiler和Cython函数

9
所以我正在尝试使用line_profiler在自己的Python脚本中对一个函数进行性能剖析,因为我想获取每行代码的时间。唯一的问题是这个函数是Cython类型的,而line_profiler无法正常工作。在最初的运行中,它只是崩溃并出现错误。然后我添加了
!python
cython: profile=True
cython: linetrace=True
cython: binding=True

在我的脚本顶部加上 line_profiler,现在它可以正常运行,但是时间和统计信息都是空的!
有没有办法使用line_profiler来分析Cython函数?
我可以对非Cython化的函数进行分析,但其速度远慢于Cython化的函数,因此我无法使用从分析中得出的信息。纯Python函数的缓慢将使我无法改进Cython函数。
以下是我想要进行分析的函数代码:
class motif_hit(object):
__slots__ = ['position', 'strand']

def __init__(self, int position=0, int strand=0):
    self.position = position
    self.strand = strand

#the decorator for line_profiler
@profile
def find_motifs_cython(list bed_list, list matrices=None, int limit=0, int mut=0):
    cdef int q = 3
    cdef list bg = [0.25, 0.25, 0.25, 0.25]
    cdef int matrices_length = len(matrices)
    cdef int results_length = 0
    cdef int results_length_shuffled = 0
    cdef np.ndarray upper_adjust_list = np.zeros(matrices_length, np.int)
    cdef np.ndarray lower_adjust_list = np.zeros(matrices_length, np.int)
    #this one need to be a list for MOODS
    cdef list threshold_list = [None for _ in xrange(matrices_length)]
    cdef list matrix_list = [None for _ in xrange(matrices_length)]
    cdef np.ndarray results_list = np.zeros(matrices_length, np.object)
    cdef int count_seq = len(bed_list)
    cdef int mat
    cdef int i, j, k
    cdef int position, strand
    cdef list result, results, results_shuffled
    cdef dict result_temp
    cdef int length
    if count_seq > 0:
        for mat in xrange(matrices_length):
            matrix_list[mat] = matrices[mat]['matrix'].tolist()
            #change that for a class
            results_list[mat] = {'kmer': matrices[mat]['kmer'],
                                 'motif_count': 0,
                                 'pos_seq_count': 0,
                                 'motif_count_shuffled': 0,
                                 'pos_seq_count_shuffled': 0,
                                 'ratio': 0,
                                 'sequence_positions': np.empty(count_seq, np.object)}
            length = len(matrices[mat]['kmer'])
            #wrong with imbalanced matrices
            upper_adjust_list[mat] = int(ceil(length / 2.0))
            lower_adjust_list[mat] = int(floor(length / 2.0))
            #upper_adjust_list[mat] = 0
            #lower_adjust_list[mat] = 0
            #-0.1 to adjust for a division floating point bug (4.99999 !< 5, but is < 4.9!)
            threshold_list[mat] = MOODS.max_score(matrix_list[mat]) - float(mut) - 0.1

        #for each sequence
        for i in xrange(count_seq):
            item = bed_list[i]
            #TODO: remove the Ns, but it might unbalance
            results = MOODS.search(str(item.sequence[limit:item.total_length - limit]), matrix_list, threshold_list, q=q, bg=bg, absolute_threshold=True, both_strands=True)
            results_shuffled = MOODS.search(str(item.sequence_shuffled[limit:item.total_length - limit]), matrix_list, threshold_list, q=q, bg=bg, absolute_threshold=True, both_strands=True)
            results = results[0:len(matrix_list)]
            results_shuffled = results_shuffled[0:len(matrix_list)]
            results_length = len(results)
            #for each matrix
            for j in xrange(results_length):
                result = results[j]
                result_shuffled = results_shuffled[j]
                upper_adjust = upper_adjust_list[j]
                lower_adjust = lower_adjust_list[j]
                result_length = len(result)
                result_length_shuffled = len(result_shuffled)
                if result_length > 0:
                    results_list[j]['pos_seq_count'] += 1
                    results_list[j]['sequence_positions'][i] = np.empty(result_length, np.object)
                    #for each motif
                    for k in xrange(result_length):
                        position = result[k][0]
                        strand = result[k][1]
                        if position >= 0:
                                strand = 0
                                adjust = upper_adjust
                        else:
                                position = -position
                                strand = 1
                                adjust = lower_adjust
                        results_list[j]['motif_count'] += 1
                        results_list[j]['sequence_positions'][i][k] = motif_hit(position + adjust + limit, strand)

                if result_length_shuffled > 0:
                    results_list[j]['pos_seq_count_shuffled'] += 1
                    #for each motif
                    for k in xrange(result_length_shuffled):
                        results_list[j]['motif_count_shuffled'] += 1

                #j = j + 1
            #i = i + 1

        for i in xrange(results_length):
            result_temp = results_list[i]
            result_temp['ratio'] = float(result_temp['pos_seq_count']) / float(count_seq)
    return results_list

我相信三重嵌套循环是主要的缓慢部分-它的任务仅仅是重新排列来自MOODS的结果,而MOODS是执行主要工作的C模块。


1
Cython函数方法是否优于其他方法? - Padraic Cunningham
不,它没有调用任何其他东西,除了通过Python绑定调用一个C模块 - 这可能是问题吗? - Justin Nelligan
您想发布代码吗?如果我理解问题正确,使用IPython可以进行性能分析。 - Padraic Cunningham
代码似乎缺少几个依赖项才能编译,motif_hitMOODS - Padraic Cunningham
如果您已经编译好了代码,请在IPython中使用%prun命令来运行该文件。 - Padraic Cunningham
显示剩余4条评论
2个回答

12
Till Hoffmann在这里提供了使用line_profiler和Cython的有用信息:如何逐行分析cython函数
我引用他的解决方案:
Robert Bradshaw帮助我使Robert Kern的line_profiler工具适用于cdef函数,我想在stackoverflow上分享结果。
简而言之,设置一个常规的.pyx文件和构建脚本,并将linetrace编译器指令传递给cythonize以启用分析和行跟踪。
from Cython.Build import cythonize

cythonize('hello.pyx', compiler_directives={'linetrace': True})

您可能还想将(未记录指令 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

3
无法导入'directive_defaults'名称,出现错误。请检查您的代码和环境设置是否正确。 - machen
1
@machen,Cython API已更改。现在您需要执行from Cython.Compiler.Options import get_directive_defaults; directive_defaults = get_directive_defaults()。请参见https://github.com/cython/cython/issues/1497。 - user2304916
我也使用了%load_ext Cython - Demetry Pascal

11

API已更改。现在为:

from Cython.Compiler.Options import get_directive_defaults
directive_defaults = get_directive_defaults()
directive_defaults['linetrace'] = True
directive_defaults['binding'] = True

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