2

巨大なテキストファイルから特定のエントリを識別し、そのエントリに対応する行を抽出するよりも優れた(より高速な)方法を探しています。ファイルの形式は次のとおりです。

>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-----------

数百から数千まで変化するため、各エントリに対応する行数(上記)を減らしました。これらすべてのエントリを保持するファイルのサイズは、100MBから600MBの範囲です。また、対応する行を識別して抽出するために通常必要なエントリの数は、数百から15,000の範囲です。現在、私はREGEXを使用してエントリ名を識別し、次の'>'記号まで対応するすべての行を抽出しています。そして、プロセスを高速化するために、Python3.0の「マルチプロセッシング」パッケージを使用します。縮小されたコードは次のとおりです。

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コアのサーバーでこれを実行しますが、処理する巨大なファイルのサイズとエントリの数によっては、スクリプトに多くの時間(数時間から1日)かかります。エントリの処理は非常に基本的であるため、プロセス全体で、特定のエントリの検索にほとんどの時間がかかります。

私が達成しようとしているのは、実行時間を可能な限り短縮することです。この分析を実行するための可能な限り最速の戦略を教えてください。

結果:

'Janne Karila'の提案(下)に従い、'NewMethod'(上)を使用した後、300エントリの実行時間は120秒で、これには巨大な密度のファイルを読み取るための85秒が含まれ、'>'==35秒で分割されます。 32コア。

REGEXで「OldMethod」(上記)を使用するのに対し、300エントリの実行時間は577秒で、これには巨大な密度のファイルを読み取るための約102秒==475秒で32コアを使用して300エントリを処理します。

巨大なファイルを読み取る時間は、12秒から102秒まで変動します。理由はわかりません。結論として、新しい方法は少なくとも10〜12倍高速です。今のところまともな改善のようです。

ありがとう

AK

4

3 に答える 3

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、代わりにループで使用しています。

于 2013-03-22T09:52:34.213 に答える
1

あなたの主な問題は、ファイル全体を正規表現しようとすることです。これには膨大な時間がかかります。

  1. ファイル全体を読むべきではなく、.readlines()代わりに使用してください
  2. ファイルを行に分割した後、行の最初の記号を 1 つだけチェックすることで新しいエントリを簡単に見つけることができます。この記号が「>」である場合にのみ、正規表現を適用してエントリ番号などを抽出します。
  3. 繰り返しますが、最初の記号が '>' になるまで行リストを反復処理し、その後停止します。

600MB のファイルの場合、20 秒以上かかることはありません。

于 2013-03-22T05:55:15.707 に答える
1

「最速」は、事前にファイルを分割するか、ファイル システム内の個々のファイルに分割するか、データ セットをデータベースにインポートすることによって実現できます。これが実際に意味があるかどうかは、データの寿命と使用パターンによって異なります。データセットを平均して 2 ~ 3 回以上処理する場合は、最初にファイル全体を分割する必要があるときに前払いし、同じデータに対する後続のクエリを多かれ少なかれ無料で取得するのが理にかなっています。

ただし、実装のオーバーヘッドを考慮に入れると、はるかに簡単な方法で実行時間を数桁削減できるという疑いがあるため、同じデータセットを何千回もクエリしない限り、大幅なオーバーホールは試みません。

単純な分割の場合、man csplit. このプログラムは少し使いにくいので、おそらくマニュアルページ以上のものを必要とするでしょう。

とにかく、分割の時間はひどく間違っているように聞こえます。ファイル全体をコアに読み取ろうとするのではなく、一度に 1 行ずつ読み取るだけで数分に短縮できます。

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

これは、約 12 行の 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
于 2013-03-22T06:12:28.040 に答える