複数の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でそれをする必要はありません、他の言語やデータベースの使用のアイデアは大歓迎です。
コードはI/Oバウンドのようです。これは、マルチプロセッシングが役に立たないことを意味します。ディスクからの読み取りに90%の時間を費やしている場合、次の読み取りを待つために7つのプロセスを追加しても何の助けにもなりません。
また、CSV読み取りモジュール(stdlibのcsv
またはNumPyやPandasのようなもの)を使用することは、単純化のための良いアイデアかもしれませんが、パフォーマンスに大きな違いをもたらすことはまずありません。
それでも、推測するだけでなく、本当にがI/Oバウンドであることを確認する価値があります。プログラムを実行して、CPU使用率が0%に近いか、100%に近いか、またはコアかどうかを確認します。アマダンがコメントで提案したことを実行し、処理のためにpass
だけでプログラムを実行し、それが時間の5%または70%をカットするかどうかを確認します。 os.open
やos.read(1024*1024)
などのループと比較してみて、それがもっと速いかどうかを確認することもできます。
Python 2.x、Pythonは、一度にバッファリングする量を推測するためにC stdioライブラリに依存しているため、強制する価値があるかもしれませんそれをさらにバッファリングします。それを行う最も簡単な方法は、いくつかの大きなbufsize
にreadlines(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
することができます。
私はこの質問が古いことを知っています。しかし、私は同様のことをしたかったので、大きなファイルを並行して読み取り、処理するのに役立つシンプルなフレームワークを作成しました。私が答えとして試したものを残します。
これがコードです。最後に例を示します
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倍の速度が得られます。