파이썬에서 큰 파일 (수만 줄)의 줄 수를 가져와야합니다. 메모리와 시간 단위로 가장 효율적인 방법은 무엇입니까?
현재 내가하는 일 :
def file_len(fname):
with open(fname) as f:
for i, l in enumerate(f):
pass
return i + 1
더 잘 할 수 있습니까?
답변
당신은 그것보다 더 나아질 수 없습니다.
결국 모든 솔루션은 전체 파일을 읽어야합니다. \n
결과를 반환해야합니다.
전체 파일을 읽지 않고 더 좋은 방법이 있습니까? 확실하지 않습니다 … 최선의 솔루션은 항상 I / O 바인딩 일 것입니다. 최선의 방법은 불필요한 메모리를 사용하지 않는 것입니다.
답변
한 줄, 아마도 꽤 빠릅니다.
num_lines = sum(1 for line in open('myfile.txt'))
답변
메모리 매핑 파일이 가장 빠른 솔루션이라고 생각합니다. 나는 네 가지 기능을 시도했다 : OP ( opcount
) 에 의해 게시 된 기능 ; 파일의 라인들에 대한 간단한 반복 ( simplecount
); 메모리 매핑 된 파일 (mmap) ( mapcount
) 이있는 판독 선 ; Mykola Kharechko에서 제공하는 버퍼 읽기 솔루션 (bufcount
)에서 .
각 기능을 5 번 실행하고 120 만 줄 텍스트 파일의 평균 런타임을 계산했습니다.
Windows XP, Python 2.5, 2GB RAM, 2GHz 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))
답변
나는 나의 명성 점수가 약간 올라갈 때까지 비슷한 질문에 이것을 게시해야했다.
이러한 모든 솔루션은 버퍼링되지 않은 (raw) 인터페이스를 사용하고, 바이트 배열을 사용하고, 자체 버퍼링을 수행하여이 실행을 상당히 빠르게하는 한 가지 방법을 무시합니다. (이것은 Python 3에만 적용됩니다. Python 2에서는 기본 인터페이스가 기본적으로 사용되거나 사용되지 않을 수 있지만 Python 3에서는 기본적으로 유니 코드로 설정됩니다.)
수정 된 버전의 타이밍 도구를 사용하면 다음 코드가 제공되는 솔루션보다 빠릅니다 (그리고 조금 더 많은 파이썬).
def rawcount(filename):
f = open(filename, 'rb')
lines = 0
buf_size = 1024 * 1024
read_f = f.raw.read
buf = read_f(buf_size)
while buf:
lines += buf.count(b'\n')
buf = read_f(buf_size)
return lines
별도의 생성기 기능을 사용하면 smidge가 더 빠르게 실행됩니다.
def _make_gen(reader):
b = reader(1024 * 1024)
while b:
yield b
b = reader(1024*1024)
def rawgencount(filename):
f = open(filename, 'rb')
f_gen = _make_gen(f.raw.read)
return sum( buf.count(b'\n') for buf in f_gen )
itertools를 사용하여 인라인으로 생성기 표현식을 사용하여 완전히 수행 할 수 있지만 꽤 이상하게 보입니다.
from itertools import (takewhile,repeat)
def rawincount(filename):
f = open(filename, 'rb')
bufgen = takewhile(lambda x: x, (f.raw.read(1024*1024) for _ in repeat(None)))
return sum( buf.count(b'\n') for buf in bufgen )
내 타이밍은 다음과 같습니다.
function average, s min, s ratio
rawincount 0.0043 0.0041 1.00
rawgencount 0.0044 0.0042 1.01
rawcount 0.0048 0.0045 1.09
bufcount 0.008 0.0068 1.64
wccount 0.01 0.0097 2.35
itercount 0.014 0.014 3.41
opcount 0.02 0.02 4.83
kylecount 0.021 0.021 5.05
simplecount 0.022 0.022 5.25
mapcount 0.037 0.031 7.46
답변
하위 프로세스를 실행하고 실행할 수 있습니다. 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])
답변
다음은 멀티 프로세싱 라이브러리를 사용하여 머신 / 코어에 라인 수를 분배하는 파이썬 프로그램입니다. 내 테스트는 8 코어 Windows 64 서버를 사용하여 2 천만 줄 파일을 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 ) )
답변
현대적인 기능을 사용 하여이 답변 과 비슷한 한 줄 bash 솔루션 subprocess.check_output
:
def line_count(filename):
return int(subprocess.check_output(['wc', '-l', filename]).split()[0])