高效的文件缓冲&;python中大型文件的扫描方法

高效的文件缓冲&;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以及一组本模块中未使用的其他小文件

对我遇到的问题的描述有点复杂,我会错误地提供更完整的信息。对于不耐烦的人,以下是我可以总结的最简单的方式:

最快(最少)的执行是什么 时间)将文本文件拆分为的方式 大小为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个小时(见下面的注释)。也许这是我用这种方法看到的最好的方法(一个完整的代码重构可能是合适的,但我想避免它,因为这种方法在代码的其他方面有一些非常特殊的优势),但我想我会把它交给社区

谢谢

  • 注意,这一次包括许多额外的计算,例如计算相反的串读取和在大约5G大小的散列上进行散列表查找
回答后结论:事实证明,与程序的其余部分相比,使用fileobj.read()然后操作生成的字符串(string.replace()等)花费的时间和内存相对较少,因此我使用了这种方法。谢谢大家

我怀疑问题在于,您有太多以字符串格式存储的数据,这对于您的用例来说是非常浪费的,以至于您的实际内存不足,并且无法进行交换。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))
前四个是我的代码,而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()