搜索名称并从大文件中提取相应的条目 - 最快的方法 - Python和Regex

时间:2013-03-22 04:05:36

标签: python regex performance runtime multiprocessing

我正在寻找一种更好(更快)的方法来识别大文本文件中的特定条目,而不是提取与该条目相对应的行。该文件格式为:

>Entry1.1
#size=1688
704 1   1   1   4
979 2   2   2   0
1220    1   1   1   4
1309    1   1   1   4
1316    1   1   1   4
1372    1   1   1   4
1374    1   1   1   4
1576    1   1   1   4
>Entry2.1
#size=6251
6110    3   1.5 0   2
6129    2   2   2   2
6136    1   1   1   4
6142    3   3   3   2
6143    4   4   4   1
6150    1   1   1   4
6152    1   1   1   4
>Entry3.2
#size=1777
AND SO ON-----------

我减少了每个条目的相应行数(上面),因为它们从几百到几千不等。包含所有这些条目的文件大小范围为100 MB到600 MB。我通常需要识别和提取相应行的条目数量从几百到15,000不等。目前我使用REGEX来识别条目名称,然后提取所有相应的行,直到下一个'>'符号。为了加快这个过程,我在python 3.0中使用了'multiprocessing'包。这是简化代码:

def OldMethod(entry):##Finds entry and extracts corresponding lines till next '>'
    patbase = '(>*%s(?![^\n]+?\d).+?)(?=>|(?:\s*\Z))'###pattern for extraction of gene entry
    found = re.findall(patbase % entry, denfile, re.DOTALL)
    if found: 
        print ('Entry found in density file\n')
        ''Do processing of corresponding line''
    return processed_result

def NewMethod(entry):##As suggested in this thread
    name = entry[1]
    block = den_dict[name]
    if found:
        ''Do processing of correponding lines in Block''

def PPResults(module,alist):##Parallel processing
    npool = Pool(int(nproc))    
    res = npool.map_async(module, alist)
    results = (res.get())###results returned in form of a list 
    return results

main():
    ##Read Density file, split by '>' and make dictionary for key and corresponding lines
    fh_in = open(density_file, 'r') ###HUGE TEXT FILE
    denfile = fh_in2.read().split('>')[1:] ###read once use repeatedly
    global den_dict
    den_dict = {}
    for ent in denfile:
        ent_splt = ent.split('\n')
        den_dict[ent_splt[0]] = ent_splt[2:-1]
    ##Use new approach with multiprocess
    results = PPResults(NewMethod, a_list)###'a_list' holds entries for that proceesing needs to be done
    for i in results:##Write Results from list to file
        fh_out.write(i)

我在超过500GB和42个核心的服务器上运行它,但是脚本需要花费很多时间(甚至一天),具体取决于要处理的大文件和数字条目的大小。在整个过程中,大部分时间都用于定位特定条目,因为条目的处理非常基础。

我想要实现的是尽可能减少运行时间。请建议我执行此分析的最快策略是什么。

结果:

按照'Janne Karila'建议(下文)并使用'NewMethod'(上图)后,300个条目的运行时间为120秒,包括85秒读取大密度文件并按“>”拆分== 35秒,使用32个核心处理300个条目。

如果在REGEX中使用'OldMethod'(上面),300个条目的运行时间为577秒,包括读取大密度文件约= 102秒= = 475秒,使用32个核心处理300个条目。

读取大文件的时间在12秒到102秒之间波动,这是我不确定的原因。最后,新方法至少快10到12倍。现在看起来像是体面的改进。

由于

AK

3 个答案:

答案 0 :(得分:1)

你的主要问题是你试图对整个文件进行正则表达式,这需要花费大量的时间。

  1. 您不应该整体阅读该文件,而是使用.readlines()
  2. 将文件拆分为行后,您可以通过仅查看行中的第一个符号来轻松找到新条目,并且仅当此符号为“>”时,才应用正则表达式来提取条目编号,依此类推
  3. 然后再次,您只是遍历行列表,直到第一个符号变为'>'然后停下来。
  4. 600MB文件的时间不应超过20秒。

答案 1 :(得分:1)

“最快可能”可以通过提前将文件拆分为文件系统中的单个文件,或者将数据集导入数据库来实现。这在实践中是否有意义取决于数据的使用寿命和使用模式。如果您平均处理的数据集超过两到三次,那么在您第一次需要时分配整个文件是有意义的,然后或多或少免费获得对相同数据的后续查询。 / p>

考虑到实施开销,并且怀疑你可以通过更简单的方法将执行时间减少几个数量级,但是,除非你查询数千个相同的数据集,否则我不会尝试进行重大改进次。

对于简单拆分,man csplit。该程序使用起来有点棘手,因此您可能需要的不仅仅是手册页。

无论如何,分裂声音的时间严重错误。你应该通过一次读一行而不是试图将整个文件读入核心来将其缩短到几分钟。

awk -v id="Entry1.1" '/^>/{p=($0 == ">" id)}p' file

这应该转化为大约十几行Python。这是一个概念证明(例如,如果您不熟悉Awk,并想了解上述内容,例如);它可能不是最优雅或惯用的Python。

import sys, fileinput, os
entry = sys.argv[1]
p = False
for line in fileinput.input(sys.argv[2:]):
    line = line.rstrip(os.linesep)
    if line[0] == '>':
        if line[1:] == entry:
            p = True
        else:
            p = False
    if p:
        print line

答案 2 :(得分:1)

您可以按>分块保存文件,并将其存储在由条目名称索引的字典中。

d = dict(chunk.split(None,1) for chunk in denfile.split('>') if chunk)

查找条目只是d["Entry1.1"]


编辑:由于您花了很多时间阅读文件,因此您应该尝试在此期间并行完成处理。您无需将整个文件存储在任何位置;只要在文件中遇到它,就将每个想要的条目发送到处理。

def NewMethod(entry):
    '''Do processing of correponding lines in Block'''

def retrieve_chunks(filename, alist):
    '''Generator that yields entries from file when entry name is in alist'''
    aset = set(alist)   #use a set for fast lookups
    chunk = None
    with open(filename) as f:
        for line in f:
            if line.startswith('>'):
                if chunk:
                    yield chunk
                name = line[1:].strip() 
                if name in aset:
                    chunk = [name] #enables capture of subsequent lines
                else:
                    chunk = None   #disables capture
            elif chunk:
                chunk.append(line)
    if chunk:
        yield chunk

main():
    npool = Pool(int(nproc))
    results = []
    for entry in retrieve_chunks(density_file, a_list): 
        results.append(npool.apply_async(NewMethod, (entry,)))

    for proxy in results:
        fh_out.write(proxy.get())

顺便说一句,请注意,如果您将生成器传递给Pool.map_async,它会在开始任何工作之前读取所有这些内容。这就是我在循环中使用apply_async的原因。