我遇到的问题描述有点复杂,我会提供更完整的信息。对于不耐烦的人,这是我可以总结的最简单的方法:
什么是最快的(最少执行 时间)分割文本文件的方法 大小为N的所有(重叠)子串(绑定N,例如36) 同时抛出换行符。
我正在编写一个模块,用于解析基于FASTA ascii的基因组格式的文件。这些文件包含所谓的'hg18'人类参考基因组,如果您愿意,可以从UCSC genome browser(go slugs!)下载。
正如您将注意到的,基因组文件由chr [1..22] .fa和chr [XY] .fa组成,以及一组未在此模块中使用的其他小文件。
已经存在几个用于解析FASTA文件的模块,例如BioPython的SeqIO。 (对不起,我发布了一个链接,但我还没有这么做。)不幸的是,我能找到的每个模块都没有按照我要做的具体操作。
我的模块需要将基因组数据(例如,'CAGTACGTCAGACTATACGGAGCTA'可能是一条线)分成每个重叠的N长度子串。让我举一个例子,使用一个非常小的文件(实际的染色体文件长度在355到2000万个字符之间)和N = 8
>>>import cStringIO >>>example_file = cStringIO.StringIO("""\ >header CAGTcag TFgcACF """) >>>for read in parse(example_file): ... print read ... CAGTCAGTF AGTCAGTFG GTCAGTFGC TCAGTFGCA CAGTFGCAC AGTFGCACF
我发现的功能在我能想到的方法中具有绝对最佳性能:
def parse(file):
size = 8 # of course in my code this is a function argument
file.readline() # skip past the header
buffer = ''
for line in file:
buffer += line.rstrip().upper()
while len(buffer) >= size:
yield buffer[:size]
buffer = buffer[1:]
这很有效,但不幸的是,用这种方式解析人类基因组还需要大约1.5小时(见下面的注释)。也许这是我将要看到的最好的方法(一个完整的代码重构可能是有序的,但我想避免它,因为这种方法在代码的其他方面有一些非常具体的优势),但我以为我会把它转交给社区。 p>
谢谢!
回答结论:事实证明,使用fileobj.read()然后操纵结果字符串(string.replace()等)花费的时间和内存相对较少程序的其余部分,所以我使用了这种方法。谢谢大家!
答案 0 :(得分:4)
你可以mmap文件并用滑动窗口开始啄它吗?我写了一个非常小的愚蠢的小程序:
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND sarnold 20919 0.0 0.0 33036 4960 pts/2 R+ 22:23 0:00 /usr/bin/python ./sliding_window.py
使用636229字节的fasta文件(通过http://biostar.stackexchange.com/questions/1759找到)花费了.383秒。
#!/usr/bin/python
import mmap
import os
def parse(string, size):
stride = 8
start = string.find("\n")
while start < size - stride:
print string[start:start+stride]
start += 1
fasta = open("small.fasta", 'r')
fasta_size = os.stat("small.fasta").st_size
fasta_map = mmap.mmap(fasta.fileno(), 0, mmap.MAP_PRIVATE, mmap.PROT_READ)
parse(fasta_map, fasta_size)
答案 1 :(得分:3)
我怀疑问题是你有这么多数据以字符串格式存储,这对你的用例来说真的很浪费,你的实际内存耗尽并且交换掉了。 128 GB 应足以避免这种情况......:)
由于您已在评论中指出您无论如何都需要存储其他信息,因此我将选择引用父字符串的单独类。我使用来自hg18的chromFa.zip的chr21.fa进行了一个简短的测试。该文件大约48MB,不到1M行。我这里只有1GB的内存,所以我只是丢弃后面的对象。因此,此测试不会显示碎片,缓存或相关问题,但我认为它应该是衡量解析吞吐量的良好起点:
import mmap
import os
import time
import sys
class Subseq(object):
__slots__ = ("parent", "offset", "length")
def __init__(self, parent, offset, length):
self.parent = parent
self.offset = offset
self.length = length
# these are discussed in comments:
def __str__(self):
return self.parent[self.offset:self.offset + self.length]
def __hash__(self):
return hash(str(self))
def __getitem__(self, index):
# doesn't currently handle slicing
assert 0 <= index < self.length
return self.parent[self.offset + index]
# other methods
def parse(file, size=8):
file.readline() # skip header
whole = "".join(line.rstrip().upper() for line in file)
for offset in xrange(0, len(whole) - size + 1):
yield Subseq(whole, offset, size)
class Seq(object):
__slots__ = ("value", "offset")
def __init__(self, value, offset):
self.value = value
self.offset = offset
def parse_sep_str(file, size=8):
file.readline() # skip header
whole = "".join(line.rstrip().upper() for line in file)
for offset in xrange(0, len(whole) - size + 1):
yield Seq(whole[offset:offset + size], offset)
def parse_plain_str(file, size=8):
file.readline() # skip header
whole = "".join(line.rstrip().upper() for line in file)
for offset in xrange(0, len(whole) - size + 1):
yield whole[offset:offset+size]
def parse_tuple(file, size=8):
file.readline() # skip header
whole = "".join(line.rstrip().upper() for line in file)
for offset in xrange(0, len(whole) - size + 1):
yield (whole, offset, size)
def parse_orig(file, size=8):
file.readline() # skip header
buffer = ''
for line in file:
buffer += line.rstrip().upper()
while len(buffer) >= size:
yield buffer[:size]
buffer = buffer[1:]
def parse_os_read(file, size=8):
file.readline() # skip header
file_size = os.fstat(file.fileno()).st_size
whole = os.read(file.fileno(), file_size).replace("\n", "").upper()
for offset in xrange(0, len(whole) - size + 1):
yield whole[offset:offset+size]
def parse_mmap(file, size=8):
file.readline() # skip past the header
buffer = ""
for line in file:
buffer += line
if len(buffer) >= size:
for start in xrange(0, len(buffer) - size + 1):
yield buffer[start:start + size].upper()
buffer = buffer[-(len(buffer) - size + 1):]
for start in xrange(0, len(buffer) - size + 1):
yield buffer[start:start + size]
def length(x):
return sum(1 for _ in x)
def duration(secs):
return "%dm %ds" % divmod(secs, 60)
def main(argv):
tests = [parse, parse_sep_str, parse_tuple, parse_plain_str, parse_orig, parse_os_read]
n = 0
for fn in tests:
n += 1
with open(argv[1]) as f:
start = time.time()
length(fn(f))
end = time.time()
print "%d %-20s %s" % (n, fn.__name__, duration(end - start))
fn = parse_mmap
n += 1
with open(argv[1]) as f:
f = mmap.mmap(f.fileno(), 0, mmap.MAP_PRIVATE, mmap.PROT_READ)
start = time.time()
length(fn(f))
end = time.time()
print "%d %-20s %s" % (n, fn.__name__, duration(end - start))
if __name__ == "__main__":
sys.exit(main(sys.argv))
的
1 parse 1m 42s
2 parse_sep_str 1m 42s
3 parse_tuple 0m 29s
4 parse_plain_str 0m 36s
5 parse_orig 0m 45s
6 parse_os_read 0m 34s
7 parse_mmap 0m 37s
前四个是我的代码,而orig是你的,后两个来自其他答案。
创建和收集用户定义的对象比元组或纯字符串成本高得多!这不应该是那么令人惊讶,但我没有意识到它会产生这么大的差别(比较#1和#3,它们在用户定义的类和元组之间真的只有不同)。您说您希望无论如何都要在字符串中存储附加信息(如offset)(如在parse和parse_sep_str情况下),因此您可以考虑在C扩展模块中实现该类型。如果你不想直接写C,请查看Cython和相关内容。
情况#1和#2是相同的:通过指向父字符串,我试图节省内存而不是处理时间,但是这个测试不能测量它。
答案 2 :(得分:3)
一些经典的IO绑定更改。
os.read
,并读入大型固定缓冲区。使用较低级别的读取操作不会重写那么多。其他的将是非常大的重写。
答案 3 :(得分:1)
我有一个处理文本文件的函数,并使用缓冲区进行读写和并行计算,并使用异步池进程。我有一个2核,8GB内存,带有gnu / linux的AMD,可以在不到1秒的时间内处理300000行,在大约4秒内处理1000000行,在大约20秒内处理大约450000行(大约220MB):
# -*- coding: utf-8 -*-
import sys
from multiprocessing import Pool
def process_file(f, fo="result.txt", fi=sys.argv[1]):
fi = open(fi, "r", 4096)
fo = open(fo, "w", 4096)
b = []
x = 0
result = None
pool = None
for line in fi:
b.append(line)
x += 1
if (x % 200000) == 0:
if pool == None:
pool = Pool(processes=20)
if result == None:
result = pool.map_async(f, b)
else:
presult = result.get()
result = pool.map_async(f, b)
for l in presult:
fo.write(l)
b = []
if not result == None:
for l in result.get():
fo.write(l)
if not b == []:
for l in b:
fo.write(f(l))
fo.close()
fi.close()
第一个参数是rceive一行,处理和返回结果的函数将写入文件,下一个是输出文件,last是输入文件(如果在脚本文件中作为第一个参数接收,则不能使用last参数输入)。