파이썬에서 큰 파일 (수만 줄)의 줄 수를 가져와야합니다. 메모리와 시간 단위로 가장 효율적인 방법은 무엇입니까?
현재 내가하는 일 :
def file_len(fname):
with open(fname) as f:
for i, l in enumerate(f):
pass
return i + 1
더 잘 할 수 있습니까?
enumerate(f, 1)
그리고 도랑 i + 1
?
파이썬에서 큰 파일 (수만 줄)의 줄 수를 가져와야합니다. 메모리와 시간 단위로 가장 효율적인 방법은 무엇입니까?
현재 내가하는 일 :
def file_len(fname):
with open(fname) as f:
for i, l in enumerate(f):
pass
return i + 1
더 잘 할 수 있습니까?
enumerate(f, 1)
그리고 도랑 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))
wccount()
가장 빠른 것 같습니다 gist.github.com/0ac760859e614cd03652
나는 나의 명성 점수가 약간 올라갈 때까지 비슷한 질문에 이것을 게시해야했다.
이러한 모든 솔루션은 버퍼링되지 않은 (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
wccount
프로세스에 서브 프로세스 쉘 wc
도구가 있습니까?
rawincount
사용하여 찾고 솔루션 덜 이상한 bufgen = iter(partial(f.raw.read, 1024*1024), b'')
대신 결합 takewhile
하고 repeat
.
하위 프로세스를 실행하고 실행할 수 있습니다. 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])
wc -l
걸리며 하위 프로세스 호출 은 ~ 5 초가 걸립니다.
shell=True
보안이 나쁘면 피하는 것이 좋습니다.
readlines
다음과 같이 Python의 파일 객체 메소드 를 사용합니다.
with open(input_file) as foo:
lines = len(foo.readlines())
파일을 열고 파일에 행 목록을 작성하고 목록의 길이를 세고 변수에 저장 한 후 파일을 다시 닫습니다.
xreadlines
반복자를 반환하므로 2.3부터 사용되지 않습니다. for line in file
명시된 대체품입니다. 참조 : docs.python.org/2/library/stdtypes.html#file.xreadlines
여기 내가 사용하는 것이 꽤 깨끗해 보입니다.
import subprocess
def count_file_lines(file_path):
"""
Counts the number of lines in a file using wc utility.
:param file_path: path to file
:return: int, no of lines
"""
num = subprocess.check_output(['wc', '-l', file_path])
num = num.split(' ')
return int(num[0])
업데이트 : 이것은 순수한 파이썬을 사용하는 것보다 약간 빠르지 만 메모리 사용 비용이 듭니다. 서브 프로세스는 명령을 실행하는 동안 상위 프로세스와 동일한 메모리 공간을 가진 새 프로세스를 분기합니다.
:-)
이것은 순수한 파이썬을 사용하여 찾은 가장 빠른 것입니다. 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), ''))
여기 답을 찾을 파이썬보다 C ++로 훨씬 느린 stdin에서 라인을 읽고 왜? 그것을 조금 조정했습니다. 행 수를 빠르게 계산하는 방법을 이해하는 데 도움이되지만 wc -l
여전히 다른 것보다 약 75 % 빠릅니다.
상수 버퍼를 재사용하는이 버전으로 약간 (4-8 %) 개선되었으므로 메모리 또는 GC 오버 헤드를 피해야합니다.
lines = 0
buffer = bytearray(2048)
with open(filename) as f:
while f.readinto(buffer) > 0:
lines += buffer.count('\n')
버퍼 크기를 가지고 놀 수 있고 약간의 향상을 볼 수 있습니다.
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
한 줄 솔루션 :
import os
os.system("wc -l filename")
내 스 니펫 :
>>> os.system('wc -l *.txt')
0 bar.txt
1000 command.txt
3 test_file.txt
1003 total
os.system()
을 변수에 저장 하고 어쨌든 후 처리 할 수 없습니다 .
위의 방법을 완료하기 위해 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이 나쁘고 스케일이 다른 모든 방법보다 훨씬 나쁘다는 것은 조금 놀랍습니다 ...
나를 위해이 변형이 가장 빠를 것입니다 :
#!/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
매우 빠릅니다.
버퍼 케이스를 다음과 같이 수정했습니다.
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 제외)도 계산됩니다.
count = max(enumerate(open(filename)))[0]
enumerate()
됩니다에 따라 계산 시작 docs.python.org/2/library/functions.html#enumerate
def line_count(path):
count = 0
with open(path) as lines:
for count, l in enumerate(lines, start=1):
pass
return count
간단한 방법 :
1)
>>> f = len(open("myfile.txt").readlines())
>>> f
430
2)
>>> f = open("myfile.txt").read().count('\n')
>>> f
430
>>>
삼)
num_lines = len(list(open('myfile.txt')))
파일을 연 결과는 반복자이며, 길이가있는 시퀀스로 변환 될 수 있습니다.
with open(filename) as f:
return len(list(f))
이것은 명시 적 루프보다 간결하며를 피합니다 enumerate
.