高效的文件缓冲&;python中大型文件的扫描方法
对我遇到的问题的描述有点复杂,我会错误地提供更完整的信息。对于不耐烦的人,以下是我可以总结的最简单的方式: 最快(最少)的执行是什么 时间)将文本文件拆分为的方式 大小为N的所有(重叠)子串(绑定N,例如36) 在扔掉换行符的时候 我正在写一个模块,它以基于FASTA ascii的基因组格式解析文件。这些文件包括所谓的“hg18”人类参考基因组,如果您愿意,可以从(go slugs!)下载 您会注意到,基因组文件由chr[1..22].fa和chr[XY].fa以及一组本模块中未使用的其他小文件组成 已有几个模块可用于解析FASTA文件,如BioPython的SeqIO。(很抱歉,我会发布一个链接,但我还没有这样做的要点。)不幸的是,我找到的每个模块都没有完成我正在尝试的特定操作 我的模块需要将基因组数据(例如,'cagtacggactatacggagcta'可以是一条线)拆分到每个重叠的N长度子串中。让我举一个例子,使用一个非常小的文件(实际的染色体文件长度在355到2000万个字符之间),N=8 这是可行的,但不幸的是,用这种方法解析人类基因组仍然需要1.5个小时(见下面的注释)。也许这是我用这种方法看到的最好的方法(一个完整的代码重构可能是合适的,但我想避免它,因为这种方法在代码的其他方面有一些非常特殊的优势),但我想我会把它交给社区 谢谢高效的文件缓冲&;python中大型文件的扫描方法,python,performance,io,bioinformatics,fasta,Python,Performance,Io,Bioinformatics,Fasta,对我遇到的问题的描述有点复杂,我会错误地提供更完整的信息。对于不耐烦的人,以下是我可以总结的最简单的方式: 最快(最少)的执行是什么 时间)将文本文件拆分为的方式 大小为N的所有(重叠)子串(绑定N,例如36) 在扔掉换行符的时候 我正在写一个模块,它以基于FASTA ascii的基因组格式解析文件。这些文件包括所谓的“hg18”人类参考基因组,如果您愿意,可以从(go slugs!)下载 您会注意到,基因组文件由chr[1..22].fa和chr[XY].fa以及一组本模块中未使用的其他小文件
- 注意,这一次包括许多额外的计算,例如计算相反的串读取和在大约5G大小的散列上进行散列表查找
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))
前四个是我的代码,而orig是你的,后两个来自其他答案
用户定义的对象比元组或普通字符串的创建和收集成本要高得多!这并不奇怪,但我没有意识到这会有这么大的不同(比较#1和#3,它们实际上只在用户定义的类和元组中有所不同)。你说你想用字符串来存储额外的信息,比如偏移量(如在PARSE和PARSESESPESTR STR案例中),所以你可以考虑在C扩展模块中实现这个类型。如果你不想直接写C,那么看看Cython和相关的
案例#1和#2应该是相同的:通过指向父字符串,我试图节省内存而不是处理时间,但是这个测试没有测量到这一点。你能映射文件并开始用滑动窗口在其中搜索吗?我写了一个愚蠢的小程序,运行起来非常小: 处理636229字节的fasta文件(通过找到)需要0.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)
#/usr/bin/python
导入mmap
导入操作系统
def解析(字符串、大小):
步幅=8
开始=字符串。查找(“\n”)
开始时<大小-步幅:
打印字符串[开始:开始+步幅]
开始+=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)
解析(fasta_映射、fasta_大小)
一些经典的IO绑定更改
- 使用较低级别的读取操作,如
os。读取
并读入大型固定缓冲区
- 使用线程/多处理,其中一个读取并缓冲其他进程
- 如果您有多个处理器/机器,请使用multiprocessing/mq在CPU之间分配处理
使用较低级别的读取操作不会像重写那么麻烦。其他的将是相当大的重写。我有一个处理文本文件的函数,在读写和并行计算中使用缓冲区,并使用进程的异步工作集池。我有一个2核8GB RAM的AMD,带有gnu/linux,可以在不到1秒的时间内处理300000行,在大约4秒的时间内处理1000000行,在大约20秒的时间内处理大约4500000行(超过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的一行、处理和返回结果将写入文件,下一个是输出文件,最后一个是输入文件(如果您在输入脚本文件中接收作为第一个参数,则不能使用最后一个参数)。谢谢您的回答,但我在两点上有不同意见。首先,这种方法使用的内存非常少。假设返回的结果不会保存很长时间(是的,但请参见第二点),那么就不会有不被收集的对象。它接近于固定内存。第二点是,我正在仔细监控RAM的使用情况,而且我还处于保密状态。(我正在运行这个程序的机器是一个拥有128G内存的野兽,一个
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
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
#!/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)
# -*- 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()