web-dev-qa-db-ja.com

大きなファイルを処理する最速の方法は?

複数の3 GBタブ区切りファイルがあります。各ファイルには2000万行があります。すべての行を独立して処理する必要があり、2つの行の間に関係はありません。私の質問は、何が速くなるのかということです。

with open() as infile:
    for line in infile:

またはB.ファイルをメモリにチャンクで読み込み、一度に250 MBずつ処理しますか?

処理はそれほど複雑ではなく、column1の値をList1、column2からList2など。列の値を一緒に追加する必要がある場合があります。

私はpython 2.7を30GBのメモリを持つLinuxボックスで使用しています。ASCIIテキスト。

物事を並行してスピードアップする方法はありますか?現在、前者の方法を使用しており、プロセスは非常に遅いです。使用するCSVReaderモジュールを使用していますか?私はPythonでそれをする必要はありません、他の言語やデータベースの使用のアイデアは大歓迎です。

27
Reise45

コードはI/Oバウンドのようです。これは、マルチプロセッシングが役に立たないことを意味します。ディスクからの読み取りに90%の時間を費やしている場合、次の読み取りを待つために7つのプロセスを追加しても何の助けにもなりません。

また、CSV読み取りモジュール(stdlibのcsvまたはNumPyやPandasのようなもの)を使用することは、単純化のための良いアイデアかもしれませんが、パフォーマンスに大きな違いをもたらすことはまずありません。

それでも、推測するだけでなく、本当にI/Oバウンドであることを確認する価値があります。プログラムを実行して、CPU使用率が0%に近いか、100%に近いか、またはコアかどうかを確認します。アマダンがコメントで提案したことを実行し、処理のためにpassだけでプログラムを実行し、それが時間の5%または70%をカットするかどうかを確認します。 os.openos.read(1024*1024)などのループと比較してみて、それがもっと速いかどうかを確認することもできます。


Python 2.x、Pythonは、一度にバッファリングする量を推測するためにC stdioライブラリに依存しているため、強制する価値があるかもしれませんそれをさらにバッファリングします。それを行う最も簡単な方法は、いくつかの大きなbufsizereadlines(bufsize)を使用することです(異なる数値を試して、ピークがどこにあるかを測定できます。私の経験では、通常、64K〜8MBの範囲はほぼ同じですが、システムによって異なります。特に、たとえばスループットが大きいがスループットが大幅に低下する恐ろしいレイテンシでネットワークファイルシステムを読み取る場合はそうです。実際の物理ドライブとOSのキャッシング。)

したがって、たとえば:

bufsize = 65536
with open(path) as infile: 
    while True:
        lines = infile.readlines(bufsize)
        if not lines:
            break
        for line in lines:
            process(line)

一方、64ビットシステムを使用している場合、最初にファイルを読み取る代わりに mmap を使用してみてください。これは確かにより良いことを保証しませんが、それはかもしれませんシステムによります。例えば:

with open(path) as infile:
    m = mmap.mmap(infile, 0, access=mmap.ACCESS_READ)

Python mmapは奇妙なオブジェクトの一種です。これはstrのように動作し、同時にfileのように動作するため、たとえば、手動で改行のスキャンを繰り返すか、ファイルであるかのようにreadlineを呼び出すことができます。両方ともPythonからファイルを行またはバッチreadlinesを実行します(Cにあるループは純粋なPythonになりました... reで、または単純なCython拡張でそれを回避できるかもしれませんが...)... I/O OSがマッピングで何をしているのかを知っていることの利点は、CPUの欠点を圧倒する可能性があります。

残念なことに、Python=は、Cでこれを最適化しようとして物事を微調整するために使用する madvise 呼び出しを公開しません(明示的に、カーネルに推測させる代わりにMADV_SEQUENTIALを設定したり、透過的な巨大ページを強制したりします)—実際にctypesから関数をlibcすることができます。

33
abarnert

私はこの質問が古いことを知っています。しかし、私は同様のことをしたかったので、大きなファイルを並行して読み取り、処理するのに役立つシンプルなフレームワークを作成しました。私が答えとして試したものを残します。

これがコードです。最後に例を示します

def chunkify_file(fname, size=1024*1024*1000, skiplines=-1):
    """
    function to divide a large text file into chunks each having size ~= size so that the chunks are line aligned

    Params : 
        fname : path to the file to be chunked
        size : size of each chink is ~> this
        skiplines : number of lines in the begining to skip, -1 means don't skip any lines
    Returns : 
        start and end position of chunks in Bytes
    """
    chunks = []
    fileEnd = os.path.getsize(fname)
    with open(fname, "rb") as f:
        if(skiplines > 0):
            for i in range(skiplines):
                f.readline()

        chunkEnd = f.tell()
        count = 0
        while True:
            chunkStart = chunkEnd
            f.seek(f.tell() + size, os.SEEK_SET)
            f.readline()  # make this chunk line aligned
            chunkEnd = f.tell()
            chunks.append((chunkStart, chunkEnd - chunkStart, fname))
            count+=1

            if chunkEnd > fileEnd:
                break
    return chunks

def parallel_apply_line_by_line_chunk(chunk_data):
    """
    function to apply a function to each line in a chunk

    Params :
        chunk_data : the data for this chunk 
    Returns :
        list of the non-None results for this chunk
    """
    chunk_start, chunk_size, file_path, func_apply = chunk_data[:4]
    func_args = chunk_data[4:]

    t1 = time.time()
    chunk_res = []
    with open(file_path, "rb") as f:
        f.seek(chunk_start)
        cont = f.read(chunk_size).decode(encoding='utf-8')
        lines = cont.splitlines()

        for i,line in enumerate(lines):
            ret = func_apply(line, *func_args)
            if(ret != None):
                chunk_res.append(ret)
    return chunk_res

def parallel_apply_line_by_line(input_file_path, chunk_size_factor, num_procs, skiplines, func_apply, func_args, fout=None):
    """
    function to apply a supplied function line by line in parallel

    Params :
        input_file_path : path to input file
        chunk_size_factor : size of 1 chunk in MB
        num_procs : number of parallel processes to spawn, max used is num of available cores - 1
        skiplines : number of top lines to skip while processing
        func_apply : a function which expects a line and outputs None for lines we don't want processed
        func_args : arguments to function func_apply
        fout : do we want to output the processed lines to a file
    Returns :
        list of the non-None results obtained be processing each line
    """
    num_parallel = min(num_procs, psutil.cpu_count()) - 1

    jobs = chunkify_file(input_file_path, 1024 * 1024 * chunk_size_factor, skiplines)

    jobs = [list(x) + [func_apply] + func_args for x in jobs]

    print("Starting the parallel pool for {} jobs ".format(len(jobs)))

    lines_counter = 0

    pool = mp.Pool(num_parallel, maxtasksperchild=1000)  # maxtaskperchild - if not supplied some weird happend and memory blows as the processes keep on lingering

    outputs = []
    for i in range(0, len(jobs), num_parallel):
        print("Chunk start = ", i)
        t1 = time.time()
        chunk_outputs = pool.map(parallel_apply_line_by_line_chunk, jobs[i : i + num_parallel])

        for i, subl in enumerate(chunk_outputs):
            for x in subl:
                if(fout != None):
                    print(x, file=fout)
                else:
                    outputs.append(x)
                lines_counter += 1
        del(chunk_outputs)
        gc.collect()
        print("All Done in time ", time.time() - t1)

    print("Total lines we have = {}".format(lines_counter))

    pool.close()
    pool.terminate()
    return outputs

たとえば、各行の単語数をカウントするファイルがある場合、各行の処理は次のようになります

def count_words_line(line):
    return len(line.strip().split())

そして、次のような関数を呼び出します:

parallel_apply_line_by_line(input_file_path, 100, 8, 0, count_words_line, [], fout=None)

これを使用すると、各行で中程度に複雑な処理を行うサイズが〜20GBのサンプルファイルで行を読み取ることで、バニラ行と比較して〜8倍の速度が得られます。

0
Deepak Saini