入力変数として行数を使用して大きなテキストファイルを分割するPythonの高速メソッド

fast method in Python to split a large text file using number of lines as input variable


質問 written by Gianni Spear @2013-03-26 19:50:16Z

: 4 : 4 : 2

行数を変数として使用してテキストファイルを分割しています。 この関数は、スピッティングされたファイルを一時ディレクトリに保存するために作成しました。 各ファイルには、最後のファイルを想定した400万行があります。

import tempfile
from itertools import groupby, count

temp_dir = tempfile.mkdtemp()

def tempfile_split(filename, temp_dir, chunk=4000000):
    with open(filename, 'r') as datafile:
        groups = groupby(datafile, key=lambda k, line=count(): next(line) // chunk)
        for k, group in groups:
            output_name = os.path.normpath(os.path.join(temp_dir + os.sep, "tempfile_%s.tmp" % k))
            for line in group:
                with open(output_name, 'a') as outfile:
                    outfile.write(line)

主な問題は、この関数の速度です。 800万行の1つのファイルを400万行の2つのファイルに分割するには、Windows OSとPython 2.7の30分以上の時間が必要です。

回答 1 written by うぬぶ @2013-03-26 19:53:02Z
6
       for line in group:
            with open(output_name, 'a') as outfile:
                outfile.write(line)

グループ内の各行に対して 、ファイルを開いて1行を書き込みます。 これは遅いです。

代わりに、グループごとに1回書き込みます。

Instead, write once per group.

コメント 1

ありがとう。lineは文字列です。「a」を「w」に変更できますか?

written by ジャンニ・スピア @2013-03-26 19:56:09Z

コメント 2

はい、 aw変更できます。

written by unutbu @2013-03-26 19:57:53Z

コメント 3

ちょっとunutbuは、outfile.write( ''。join(group))をoutfile.write(line)に変更します。これは、<'' ''。join(group)>>を使用すると、関数が各行に1つのファイルを保存するためです(〜800万ファイル)

written by ジャンニスピア @2013-03-26 20:24:46Z

コメント 4

Gianni、 outfile.write(''.join(group))を保持しfor line in groupfor line in group完全に削除します。

written by unutbu @2013-03-26 20:38:34Z

コメント 5

今では超高速です!!!

written by ジャンニ・スピア @2013-03-26 20:54:28Z

回答 2 written by ウイング・タン・ウォン @2013-03-26 20:24:11Z
1

ファイルの長さを実行し、ファイルを半分に分割するために、800万行のファイル(アップタイム行)で簡単なテストを行いました。 基本的に、1回のパスで行数を取得し、2回目のパスで分割書き込みを実行します。

私のシステムでは、最初のパスの実行にかかる時間は約2〜3秒でした。 実行と分割ファイルの書き込みを完了するためにかかった合計時間は21秒未満でした。

OPの投稿でランバ機能を実装しませんでした。 以下で使用されるコード:

#!/usr/bin/env python

import sys
import math

infile = open("input","r")

linecount=0

for line in infile:
    linecount=linecount+1

splitpoint=linecount/2

infile.close()

infile = open("input","r")
outfile1 = open("output1","w")
outfile2 = open("output2","w")

print linecount , splitpoint

linecount=0

for line in infile:
    linecount=linecount+1
    if ( linecount <= splitpoint ):
        outfile1.write(line)
    else:
        outfile2.write(line)

infile.close()
outfile1.close()
outfile2.close()

いいえ、パフォーマンステストやコードエレガンステストに勝つことはありません。 :)しかし、パフォーマンスのボトルネックとなる何か、ファイルをメモリにキャッシュし、スワップの問題を強制するラムダ関数、またはファイルの行が非常に長いことを除いて、なぜそれが30かかるのかわかりません800万行のファイルの読み取り/分割にかかる時間。

編集:

私の環境:Mac OS X、ストレージは単一のFW800に接続されたハードドライブでした。 ファイルシステムのキャッシュの利点を避けるために、ファイルは新しく作成されました。

回答 3 written by dawg @2013-03-26 21:07:00Z
1

コンテキストマネージャーでtempfile.NamedTemporaryFileを直接使用できます。

import tempfile
import time
from itertools import groupby, count

def tempfile_split(filename, temp_dir, chunk=4*10**6):
    fns={}
    with open(filename, 'r') as datafile:
        groups = groupby(datafile, key=lambda k, line=count(): next(line) // chunk)
        for k, group in groups:
            with tempfile.NamedTemporaryFile(delete=False,
                           dir=temp_dir,prefix='{}_'.format(str(k))) as outfile:
                outfile.write(''.join(group))
                fns[k]=outfile.name   
    return fns                     

def make_test(size=8*10**6+1000):
    with tempfile.NamedTemporaryFile(delete=False) as fn:
        for i in xrange(size):
            fn.write('Line {}\n'.format(i))

    return fn.name        

fn=make_test()
t0=time.time()
print tempfile_split(fn,tempfile.mkdtemp()),time.time()-t0   

私のコンピューターでは、 tempfile_split部分は3.6秒で実行されます。 OS Xです。

コメント 1

コンテキストマネージャを使用すると状況が変わる理由を説明できますか

written by オオカミ @2013-03-26 21:21:08Z

回答 4 written by radtek @2018-02-02 07:44:31Z
0

LinuxまたはUNIX環境にいる場合は、少しチートして、Pythonの内部からsplitコマンドを使用できます。 私のためにトリックを行い、非常に高速です:

def split_file(file_path, chunk=4000):

    p = subprocess.Popen(['split', '-a', '2', '-l', str(chunk), file_path,
                          os.path.dirname(file_path) + '/'], stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    p.communicate()
    # Remove the original file if required
    try:
        os.remove(file_path)
    except OSError:
        pass
    return True