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

时间:2015-12-15 01:49:25

标签: python c python-3.x cython

我有一个相对简单的问题:在基因组中给出一个位置,在那时返回基因的名称。

我现在解决这个问题的方法是在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

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

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

我相信我必须有一个更有效的方法,但我不是C程序员,我对cython很新。我的猜测是我可以建立一种纯粹的C结构来保存基因名称以及起始值和结束值。然后我可以将lookup()转换为调用另一个名为_lookup()的cdef函数的函数,并使用该cdef函数来执行该实际查询。

在一个理想的世界中,整个结构可以存在于内存中,占用的内存少于2GB,大约2,000,000个条目(每个条目有两个整数和一个字符串)。

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

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

谢谢!

2 个答案:

答案 0 :(得分:4)

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

cdef class StartEnd:
    cdef public int start, end

    def __init__(self, start, end):
        self.start = start
        self.end = end

(你也可以玩更改整数类型以节省更多的尺寸)。我不建议删除Python词典,因为它们易于使用,并且(我相信)已经过优化,对于字符串键的(通常在Python中)来说是合理有效的。

我们可以使用sys.getsizeof来估算粗略的尺寸节省。 (请注意,这对内置类和Cython类很有效,但对Python类不太好,所以不要太信任它。还要注意结果是依赖于平台的,所以你的可能会有所不同。)< / p>

>>> sys.getsizeof((1,2)) # tuple
64
>>> sys.getsizeof(1) # Python int
28

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

>>> sys.getsizeof(StartEnd(1,2)) # my custom class
24

(24是有道理的:它是PyObject_Head(16个字节:64位整数和指针)+ 2个32位整数)。

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

答案 1 :(得分:2)

在我使用amendcython的经验有限的情况下,将numpy用于“内部”是最有利可图的。不需要使用Python / numpy代码的计算。它们是迭代,可以转换为紧凑和快速的C代码。

这里重写了你的代码,拆分两个类可以重新编写为Cython / C结构:

cython

这些将由高级Python函数(或类)导入和使用,不需要在Cython .pdx文件中:

# cython candidate, like DavidW's StartEnd
class Gene(object):
    def __init__(self, values):
        self.chr = values[0]
        self.start = int(values[1])
        self.end = int(values[2])
        self.gene = values[3]
    def find(self, i):
        return self.start<=i<self.end
    def __repr__(self):
        return "%s(%s, %d:%d)"%(self.chr,self.gene,self.start,self.end)

# cython candidate
class Chrom(list):
    def add(self, aGene):
        self.append(aGene)
    def find(self, loc):
        # find - analogous to string find?
        i = int(loc)
        for gene in self:
             if gene.find(i):
                 return gene  # gene.gene
        return None
    def __repr__(self):
        astr = []
        for gene in self:
            astr += [repr(gene)]
        return '; '.join(astr)

与文件或文本模拟一起使用:

from collections import defaultdict
def load(anIterable):
    data = defaultdict(Chrom)
    for line in anIterable:
        f = line.rstrip().split(',')
        if len(f)<4:
            continue
        aGene = Gene(f)
        data[aGene.chr].add(aGene)
    return data

我可以定义一个# befile = 'filename' # with open(bedfile) as infile: # data = load(infile) txt = """\ A, 1,4,a A, 4,8,b B, 3,5,a B, 5,10,c """ data = load(txt.splitlines()) print data # defaultdict(<class '__main__.Chrom'>, { # 'A': A(a, 1:4); A(b, 4:8), # 'B': B(a, 3:5); B(c, 5:10)}) print 3, data['A'].find(3) # a gene print 9, data['B'].find(9) # c gene print 11,data['B'].find(11) # none 函数,该函数遵循一个方法(如果可用),否则使用它自己的函数。这类似于委托给方法的numpy函数:

find

使用普通列表数据结构测试def find(chrdata, loc): # find - analogous to string find? fn = getattr(chrdata, 'find',None) if fn is None: #raise AttributeError(chrdata,'does not have find method') def fn(loc): i = int(loc) for gene in chrdata: if gene.find(i): return gene # gene.gene return None return fn(loc) print 3, find(data['A'],3)

find