cython:减少class的大小,减少内存使用,提高速度
cython: reducing the size of a class, reduce memory use, improve speed
我有一个相对简单的问题:给定基因组中的一个位置,return 那个位置的基因名称。
我现在解决这个问题的方法是在 cython::
中使用以下 class
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,虽然整个相关 class 在上面。
代码的问题在于生成的 class/dictionary 占用了人类基因组的大量内存,其中包含 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
不过,我仍然认为上面的 class 可以用 cython 进行优化,使字典在内存中更小,查找速度更快,欢迎任何建议。
谢谢!
为了扩展我在评论中的建议,不要将 (start,end)
存储为元组,而是将其存储为简单的 Cython 定义的 class:
cdef class StartEnd:
cdef public int start, end
def __init__(self, start, end):
self.start = start
self.end = end
(您也可以尝试更改整数类型以节省更多空间)。我不建议摆脱 Python 词典,因为它们易于使用,并且(我相信)经过优化以在字符串键的情况下(在 Python 中很常见)相当有效。
我们可以使用 sys.getsizeof
来估算粗略的大小节省。 (请注意,这对内置 classes 和 Cython classes 效果很好,但对 Python classes 效果不佳,所以不要太相信它远。另请注意,结果取决于平台,因此您的结果可能略有不同)。
>>> 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倍,我认为这是一个好的开始。
根据我对 cython
和 numpy
的有限经验,使用 cython
进行不需要使用 [=33] 的 'inner' 计算是最有利可图的=] 代码。它们是可以转换为紧凑且快速的 C 代码的迭代。
这里重写了您的代码,拆分出两个 classes 可以重铸为 Cython/C 结构:
# 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)
这些将由不需要在 Cython .pdx 文件中的高级 Python 函数(或 class)导入和使用:
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
我可以定义一个 find
函数,该函数如果可用则遵从某个方法,否则使用它自己的方法。这类似于委托给方法的 numpy 函数:
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
:
def loadlist(anIterable):
# collect data in ordinary list
data = defaultdict(list)
for line in anIterable:
f = line.rstrip().split(',')
if len(f)<4:
continue
aGene = Gene(f)
data[aGene.chr].append(aGene)
return data
data = loadlist(txt.splitlines())
print 3, find(data['A'],3)
我有一个相对简单的问题:给定基因组中的一个位置,return 那个位置的基因名称。
我现在解决这个问题的方法是在 cython::
中使用以下 classclass 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,虽然整个相关 class 在上面。
代码的问题在于生成的 class/dictionary 占用了人类基因组的大量内存,其中包含 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
不过,我仍然认为上面的 class 可以用 cython 进行优化,使字典在内存中更小,查找速度更快,欢迎任何建议。
谢谢!
为了扩展我在评论中的建议,不要将 (start,end)
存储为元组,而是将其存储为简单的 Cython 定义的 class:
cdef class StartEnd:
cdef public int start, end
def __init__(self, start, end):
self.start = start
self.end = end
(您也可以尝试更改整数类型以节省更多空间)。我不建议摆脱 Python 词典,因为它们易于使用,并且(我相信)经过优化以在字符串键的情况下(在 Python 中很常见)相当有效。
我们可以使用 sys.getsizeof
来估算粗略的大小节省。 (请注意,这对内置 classes 和 Cython classes 效果很好,但对 Python classes 效果不佳,所以不要太相信它远。另请注意,结果取决于平台,因此您的结果可能略有不同)。
>>> 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倍,我认为这是一个好的开始。
根据我对 cython
和 numpy
的有限经验,使用 cython
进行不需要使用 [=33] 的 'inner' 计算是最有利可图的=] 代码。它们是可以转换为紧凑且快速的 C 代码的迭代。
这里重写了您的代码,拆分出两个 classes 可以重铸为 Cython/C 结构:
# 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)
这些将由不需要在 Cython .pdx 文件中的高级 Python 函数(或 class)导入和使用:
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
我可以定义一个 find
函数,该函数如果可用则遵从某个方法,否则使用它自己的方法。这类似于委托给方法的 numpy 函数:
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
:
def loadlist(anIterable):
# collect data in ordinary list
data = defaultdict(list)
for line in anIterable:
f = line.rstrip().split(',')
if len(f)<4:
continue
aGene = Gene(f)
data[aGene.chr].append(aGene)
return data
data = loadlist(txt.splitlines())
print 3, find(data['A'],3)