cython:减少类的大小,减少内存使用,提高速度

Mik*_*cre 4 c python cython python-3.x

我有一个相对简单的问题:给定基因组中的位置,返回该点的基因名称。

我现在解决这个问题的方法是在 cython: 中使用以下类:

class BedFile():
    """ A Lookup Object """
    def __init__(self, bedfile):
        self.dict = {}
        cdef int start, end
        with open(bedfile) as infile:
            for line in infile:
                f = line.rstrip().split('\t')
                if len(f) < 4:
                    continue
                chr   = f[0]
                start = int(f[1])
                end   = int(f[2])
                gene  = f[3]
                if chr not in self.dict:
                    self.dict[chr] = {}
                self.dict[chr][gene] = (start, end)

    def lookup(self, chromosome, location):
        """ Lookup your gene. Returns the gene name """
        cdef int l
        l = int(location)
        answer = ''
        for k, v in self.dict[chromosome].items():
            if v[0] < l < v[1]:
                answer = k
                break
        if answer:
            return answer
        else:
            return None
Run Code Online (Sandbox Code Playgroud)

完整的项目在这里: https: //github.com/MikeDacre/python_bed_lookup,尽管整个相关类都在上面。

代码的问题在于,生成的类/字典占用了人类基因组的大量内存,其中包含 1.1 亿个基因(这是一个 1.1 亿行长的文本文件)。大约两分钟后,当它达到 16GB 内存时,我在构建字典的过程中杀死了init函数。任何使用那么多内存的东西基本上都是无用的。

我确信我必须有一种更有效的方法来做到这一点,但我不是 C 程序员,而且我对 cython 很陌生。我的猜测是,我可以构建某种纯 C 结构来保存基因名称以及起始值和终止值。然后我可以将 Lookup() 转换为一个调用另一个名为 _lookup() 的 cdef 函数的函数,并使用该 cdef 函数执行实际查询。

在理想情况下,整个结构可以存在于内存中,并且大约 2,000,000 个条目(每个条目包含两个整数和一个字符串)占用不到 2GB 的内存。

编辑:我想出了如何使用 sqlite 对于大文件有效地执行此操作,要查看 sqlite 的完整代码,请参阅此处:https: //github.com/MikeDacre/python_bed_lookup

但是,我仍然认为上面的类可以使用 cython 进行优化,以使字典在内存中更小并且查找速度更快,任何建议都值得赞赏。

谢谢!

Dav*_*idW 6

为了稍微扩展一下我在评论中的建议,不要将其存储(start,end)为元组,而是将其存储为简单的 Cython 定义的类:

cdef class StartEnd:
    cdef public int start, end

    def __init__(self, start, end):
        self.start = start
        self.end = end
Run Code Online (Sandbox Code Playgroud)

(您还可以更改整数类型以节省更多大小)。我不建议放弃 Python 字典,因为它们很容易使用,并且(我相信)经过优化,对于字符串键(Python 中常见)的情况相当有效。

我们可以使用 来粗略估计节省的大小sys.getsizeof。(请注意,这对于内置类和 Cython 类来说效果很好,但对于 Python 类来说效果不太好,所以不要太相信它。另请注意,结果取决于平台,因此您的结果可能会略有不同)。

>>> sys.getsizeof((1,2)) # tuple
64
>>> sys.getsizeof(1) # Python int
28
Run Code Online (Sandbox Code Playgroud)

(因此每个元组包含64+28+28=120字节)

>>> sys.getsizeof(StartEnd(1,2)) # my custom class
24
Run Code Online (Sandbox Code Playgroud)

(24 有意义:它是PyObject_Head(16 个字节:一个 64 位整数和一个指针)+ 2 个 32 位整数)。

因此,小了5倍,我认为这是一个好的开始。