Python で大きなファイル (数十万行) の行数を取得する必要があります。メモリと時間の両方で最も効率的な方法は何ですか?
現時点で私は:
def file_len(fname):
with open(fname) as f:
for i, l in enumerate(f):
pass
return i + 1
もっとうまくやることは可能ですか?
Python で大きなファイル (数十万行) の行数を取得する必要があります。メモリと時間の両方で最も効率的な方法は何ですか?
現時点で私は:
def file_len(fname):
with open(fname) as f:
for i, l in enumerate(f):
pass
return i + 1
もっとうまくやることは可能ですか?
おそらくかなり速い1行:
num_lines = sum(1 for line in open('myfile.txt'))
あなたはそれよりも良くなることはできません。
結局のところ、ソリューションはファイル全体を読み取り、ファイルの数を把握し\n
て、その結果を返す必要があります。
ファイル全体を読み取らずにそれを行うより良い方法はありますか? わかりません...最善の解決策は常にI / Oバウンドです。不要なメモリを使用しないようにすることが最善ですが、それはカバーされているようです。
メモリ マップド ファイルが最速のソリューションになると思います。私は4つの機能を試しました.OP(opcount
)によって投稿された機能。ファイル内の行に対する単純な反復 ( simplecount
); メモリマップされたフィールド (mmap) を持つ readline ( mapcount
); Mykola Kharechko ( bufcount
) によって提供されたバッファー読み取りソリューション。
各関数を 5 回実行し、120 万行のテキスト ファイルの平均実行時間を計算しました。
Windows XP、Python 2.5、2 GB RAM、2 GHz AMD プロセッサ
ここに私の結果があります:
mapcount : 0.465599966049
simplecount : 0.756399965286
bufcount : 0.546800041199
opcount : 0.718600034714
編集: Python 2.6 の数値:
mapcount : 0.471799945831
simplecount : 0.634400033951
bufcount : 0.468800067902
opcount : 0.602999973297
そのため、Windows/Python 2.6 ではバッファ読み取り戦略が最速のようです。
コードは次のとおりです。
from __future__ import with_statement
import time
import mmap
import random
from collections import defaultdict
def mapcount(filename):
f = open(filename, "r+")
buf = mmap.mmap(f.fileno(), 0)
lines = 0
readline = buf.readline
while readline():
lines += 1
return lines
def simplecount(filename):
lines = 0
for line in open(filename):
lines += 1
return lines
def bufcount(filename):
f = open(filename)
lines = 0
buf_size = 1024 * 1024
read_f = f.read # loop optimization
buf = read_f(buf_size)
while buf:
lines += buf.count('\n')
buf = read_f(buf_size)
return lines
def opcount(fname):
with open(fname) as f:
for i, l in enumerate(f):
pass
return i + 1
counts = defaultdict(list)
for i in range(5):
for func in [mapcount, simplecount, bufcount, opcount]:
start_time = time.time()
assert func("big_file.txt") == 1209138
counts[func].append(time.time() - start_time)
for key, vals in counts.items():
print key.__name__, ":", sum(vals) / float(len(vals))
サブプロセスを実行して実行できますwc -l filename
import subprocess
def file_len(fname):
p = subprocess.Popen(['wc', '-l', fname], stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
result, err = p.communicate()
if p.returncode != 0:
raise IOError(err)
return int(result.strip().split()[0])
マルチプロセッシング ライブラリを使用して、ライン カウントを複数のマシン/コアに分散する Python プログラムを次に示します。私のテストでは、8 コアの Windows 64 サーバーを使用して、2000 万行のファイルのカウントが 26 秒から 7 秒に改善されました。注: メモリ マッピングを使用しないと、動作が大幅に遅くなります。
import multiprocessing, sys, time, os, mmap
import logging, logging.handlers
def init_logger(pid):
console_format = 'P{0} %(levelname)s %(message)s'.format(pid)
logger = logging.getLogger() # New logger at root level
logger.setLevel( logging.INFO )
logger.handlers.append( logging.StreamHandler() )
logger.handlers[0].setFormatter( logging.Formatter( console_format, '%d/%m/%y %H:%M:%S' ) )
def getFileLineCount( queues, pid, processes, file1 ):
init_logger(pid)
logging.info( 'start' )
physical_file = open(file1, "r")
# mmap.mmap(fileno, length[, tagname[, access[, offset]]]
m1 = mmap.mmap( physical_file.fileno(), 0, access=mmap.ACCESS_READ )
#work out file size to divide up line counting
fSize = os.stat(file1).st_size
chunk = (fSize / processes) + 1
lines = 0
#get where I start and stop
_seedStart = chunk * (pid)
_seekEnd = chunk * (pid+1)
seekStart = int(_seedStart)
seekEnd = int(_seekEnd)
if seekEnd < int(_seekEnd + 1):
seekEnd += 1
if _seedStart < int(seekStart + 1):
seekStart += 1
if seekEnd > fSize:
seekEnd = fSize
#find where to start
if pid > 0:
m1.seek( seekStart )
#read next line
l1 = m1.readline() # need to use readline with memory mapped files
seekStart = m1.tell()
#tell previous rank my seek start to make their seek end
if pid > 0:
queues[pid-1].put( seekStart )
if pid < processes-1:
seekEnd = queues[pid].get()
m1.seek( seekStart )
l1 = m1.readline()
while len(l1) > 0:
lines += 1
l1 = m1.readline()
if m1.tell() > seekEnd or len(l1) == 0:
break
logging.info( 'done' )
# add up the results
if pid == 0:
for p in range(1,processes):
lines += queues[0].get()
queues[0].put(lines) # the total lines counted
else:
queues[0].put(lines)
m1.close()
physical_file.close()
if __name__ == '__main__':
init_logger( 'main' )
if len(sys.argv) > 1:
file_name = sys.argv[1]
else:
logging.fatal( 'parameters required: file-name [processes]' )
exit()
t = time.time()
processes = multiprocessing.cpu_count()
if len(sys.argv) > 2:
processes = int(sys.argv[2])
queues=[] # a queue for each process
for pid in range(processes):
queues.append( multiprocessing.Queue() )
jobs=[]
prev_pipe = 0
for pid in range(processes):
p = multiprocessing.Process( target = getFileLineCount, args=(queues, pid, processes, file_name,) )
p.start()
jobs.append(p)
jobs[0].join() #wait for counting to finish
lines = queues[0].get()
logging.info( 'finished {} Lines:{}'.format( time.time() - t, lines ) )
次のように、 Python のファイル オブジェクト メソッドを使用readlines
します。
with open(input_file) as foo:
lines = len(foo.readlines())
これにより、ファイルが開き、ファイル内の行のリストが作成され、リストの長さがカウントされ、それが変数に保存され、ファイルが再び閉じられます。
これは、純粋な python を使用して見つけた最速のものです。私のコンピュータでは 2**16 がスイート スポットのように見えますが、バッファを設定することで必要な量のメモリを使用できます。
from functools import partial
buffer=2**16
with open(myfile) as f:
print sum(x.count('\n') for x in iter(partial(f.read,buffer), ''))
私はここで答えを見つけた 少しだけ微調整しました。行をすばやくカウントする方法を理解するには、非常に良い読み物ですwc -l
が、それでも他の何よりも約 75% 高速です。
def file_len(full_path):
""" Count number of lines in a file."""
f = open(full_path)
nr_of_lines = sum(1 for line in f)
f.close()
return nr_of_lines
1 行のソリューション:
import os
os.system("wc -l filename")
私のスニペット:
>>> os.system('wc -l *.txt')
0 bar.txt
1000 command.txt
3 test_file.txt
1003 total
num_lines = sum(1 for line in open('my_file.txt'))
おそらく最良の方法です。これに代わる方法は次のとおりです。
num_lines = len(open('my_file.txt').read().splitlines())
両者の性能比較はこちら
In [20]: timeit sum(1 for line in open('Charts.ipynb'))
100000 loops, best of 3: 9.79 µs per loop
In [21]: timeit len(open('Charts.ipynb').read().splitlines())
100000 loops, best of 3: 12 µs per loop
このバージョンでは、定数バッファーを再利用するため、メモリやGCのオーバーヘッドを回避できるため、わずかな改善(4〜8%)が得られました。
lines = 0
buffer = bytearray(2048)
with open(filename) as f:
while f.readinto(buffer) > 0:
lines += buffer.count('\n')
バッファサイズを試してみて、少し改善されるかもしれません。
私にとっては、このバリアントが最速になります:
#!/usr/bin/env python
def main():
f = open('filename')
lines = 0
buf_size = 1024 * 1024
read_f = f.read # loop optimization
buf = read_f(buf_size)
while buf:
lines += buf.count('\n')
buf = read_f(buf_size)
print lines
if __name__ == '__main__':
main()
理由: 行ごとに読み取るよりもバッファリングの方が速く、string.count
また非常に高速です
上記の方法を完了するために、 fileinput モジュールを使用してバリアントを試しました。
import fileinput as fi
def filecount(fname):
for line in fi.input(fname):
pass
return fi.lineno()
上記のすべてのメソッドに 60mil 行のファイルを渡します。
mapcount : 6.1331050396
simplecount : 4.588793993
opcount : 4.42918205261
filecount : 43.2780818939
bufcount : 0.170812129974
fileinput がそれほど悪く、他のすべての方法よりもはるかに悪いことに少し驚いています...
このコードはより短く、より明確です。それはおそらく最良の方法です:
num_lines = open('yourfile.ext').read().count('\n')
次のようにバッファ ケースを変更しました。
def CountLines(filename):
f = open(filename)
try:
lines = 1
buf_size = 1024 * 1024
read_f = f.read # loop optimization
buf = read_f(buf_size)
# Empty file
if not buf:
return 0
while buf:
lines += buf.count('\n')
buf = read_f(buf_size)
return lines
finally:
f.close()
空のファイルと最後の行 (\n なし) もカウントされるようになりました。
Linux の Python で安価に行数を取得したい場合は、次の方法をお勧めします。
import os
print os.popen("wc -l file_path").readline().split()[0]
file_path は、抽象ファイル パスまたは相対パスの両方にすることができます。これが役立つことを願っています。
すでに多くの答えがありますが、残念ながらそれらのほとんどは、ほとんど最適化できない問題に関する小さな経済です...
私は、行数がソフトウェアのコア機能であるいくつかのプロジェクトに取り組み、膨大な数のファイルをできるだけ速く処理することが最も重要でした。
行数の主なボトルネックは I/O アクセスです。改行文字を検出するために各行を読み取る必要があるため、これを回避する方法はありません。2 番目の潜在的なボトルネックはメモリ管理です。一度にロードする量が多いほど、処理が速くなりますが、このボトルネックは最初のボトルネックに比べて無視できます。
したがって、行カウント関数の処理時間を短縮する主な方法は 3 つありますが、gc コレクションの無効化やその他の細かな管理トリックなどの小さな最適化は別として、次のようになります。
ハードウェア ソリューション:主要かつ最も明白な方法は、非プログラムです。非常に高速な SSD/フラッシュ ハード ドライブを購入します。これまでのところ、これが最大のスピードブーストを得る方法です.
データ準備ソリューション:処理するファイルを生成するか、生成方法を変更できる場合、またはそれらを前処理することが許容される場合は、最初に改行を UNIX スタイル ( \n
) に変換します。これにより、Windows またはMacOS スタイル (大きな節約にはなりませんが、簡単に得られます)、そして次に最も重要なこととして、固定長の行を書くことができる可能性があります。可変長が必要な場合は、いつでも小さな行を埋め込むことができます。このようにして、ファイルサイズの合計から行数を即座に計算できるため、アクセスがはるかに高速になります。多くの場合、問題に対する最善の解決策は、最終目的により適合するように前処理することです。
並列化 + ハードウェア ソリューション:複数のハードディスク (および可能であれば SSD フラッシュ ディスク) を購入できる場合は、並列化を利用して、ディスク間でバランスのとれた方法でファイルを保存することにより (最も簡単なのは合計サイズでバランスを取ることです)、1 つのディスクの速度を超えることさえできます。 、そしてそれらすべてのディスクから並行して読み取ります。そうすれば、所有しているディスクの数に比例して乗数が増加することが期待できます。複数のディスクを購入できない場合、並列化は役に立たない可能性があります (プロ仕様のディスクのようにディスクに複数の読み取りヘッダーがある場合を除きますが、それでもディスクの内部キャッシュ メモリと PCB 回路がボトルネックになる可能性があります)。すべてのヘッドを並行して完全に使用することを防ぎます。さらに、このハードドライブ用の特定のコードを考案する必要があります。ファイルを異なるヘッドの下のクラスターに保存し、後で異なるヘッドでそれらを読み取ることができるように、正確なクラスターマッピングを知る必要があるため、使用します)。実際、シーケンシャル読み取りはほとんどの場合ランダム読み取りより高速であり、単一ディスクでの並列化のパフォーマンスはシーケンシャル読み取りよりもランダム読み取りに似ていることが一般的に知られています (たとえば、CrystalDiskMark を使用して両方の側面でハード ドライブの速度をテストできます)。 .
これらのどれも選択肢にない場合は、行カウント関数の速度を数パーセント向上させるためにマイクロ管理のトリックに頼ることしかできませんが、実際に重要なことは何も期待できません. むしろ、微調整に費やす時間は、目に見える速度向上の見返りに比べて不釣り合いになると予想できます。
これはどうですか
def file_len(fname):
counts = itertools.count()
with open(fname) as f:
for _ in f: counts.next()
return counts.next()
このワンライナーはどうですか:
file_length = len(open('myfile.txt','r').read().split('\n'))
この方法を使用して 3900 行のファイルで時間を計るのに 0.003 秒かかります
def c():
import time
s = time.time()
file_length = len(open('myfile.txt','r').read().split('\n'))
print time.time() - s
これはどう?
import fileinput
import sys
counter=0
for line in fileinput.input([sys.argv[1]]):
counter+=1
fileinput.close()
print counter
ファイルを開いた結果はイテレータであり、長さを持つシーケンスに変換できます。
with open(filename) as f:
return len(list(f))
これは、明示的なループよりも簡潔であり、enumerate
.
count = max(enumerate(open(filename)))[0]
def line_count(path):
count = 0
with open(path) as lines:
for count, l in enumerate(lines, start=1):
pass
return count
print open('file.txt', 'r').read().count("\n") + 1
os.path
モジュールは次の方法で使用できます。
import os
import subprocess
Number_lines = int( (subprocess.Popen( 'wc -l {0}'.format( Filename ), shell=True, stdout=subprocess.PIPE).stdout).readlines()[0].split()[0] )
、Filename
ファイルの絶対パスです。
最初の 100 行と最後の 100 行を読み取って平均行長を推定し、その数値で合計ファイル サイズを割りませんか? 正確な値が必要ない場合は、これでうまくいく可能性があります。
別の可能性:
import subprocess
def num_lines_in_file(fpath):
return int(subprocess.check_output('wc -l %s' % fpath, shell=True).strip().split()[0])
なぜ次のことがうまくいかないのですか?
import sys
# input comes from STDIN
file = sys.stdin
data = file.readlines()
# get total number of lines in file
lines = len(data)
print lines
この場合、len関数は、長さを決定する手段として入力行を使用します。
これはどうですか?
import sys
sys.stdin=open('fname','r')
data=sys.stdin.readlines()
print "counted",len(data),"lines"