Python에서 대용량 파일의 라인 수를 저렴하게 얻는 방법
나는 파이톤으로 큰 파일(수십만 줄)의 라인 카운트를 구해야 한다.기억력과 시간 면에서 가장 효율적인 방법은 무엇인가?
그 순간 나는 다음과 같이 한다.
def file_len(fname):
with open(fname) as f:
for i, l in enumerate(f):
pass
return i + 1
더 잘 할 수 있을까?
한 줄, 아마 꽤 빠를 것이다.
num_lines = sum(1 for line in open('myfile.txt'))
이보다 더 좋을 수는 없다.
결국, 어떤 솔루션이든 전체 파일을 읽고 얼마나 많은 파일을 읽어야 하는지를 알아내야 한다.\n
가지고 있고, 그 결과를 돌려준다.
파일 전체를 읽지 않고 그렇게 하는 더 좋은 방법이 있니?잘 모르겠는데...가장 좋은 해결책은 항상 I/O-bound일 것이며, 불필요한 메모리를 사용하지 않도록 하는 것이 최선일 것이다. 그러나 그것은 당신이 가지고 있는 것처럼 보인다.
나는 메모리 매핑 파일이 가장 빠른 해결책이 될 것이라고 믿는다.OP가 게시한 기능(OP가 게시한 기능)의 네 가지 기능을 시도해 보았다.opcount
반복 (; ); 파의 에에 반복 (.simplecount
);메모리가 저장되는 파일(mmap)로 된 리드라인(readline)mapcount
(;;; 미콜라 카레코 가 (Mykola Karechkoga)가 솔루션은 solution는버퍼퍼는는는는(((는는는(()이다.bufcount
).
각 함수를 다섯 번 실행했고, 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))
평판점수가 조금 뛸 때까지 비슷한 질문에 이 글을 올려야 했다.
이러한 모든 솔루션은 이 실행 속도를 상당히 빠르게 하기 위한 한 가지 방법, 즉 버퍼링되지 않은 (원시) 인터페이스 사용, 바이테레이 사용 및 자체 버퍼링 수행 방법을 무시한다. (이것은 파이썬 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 )
이 작업은 iertools를 사용하여 인라인에 있는 생성자 표현으로 완전히 수행될 수 있지만, 매우 이상하게 보인다.
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개의 코어 윈도우 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 ) )
이 답변과 유사한 한 줄 바시 솔루션으로, 현대적인 것을 사용한다.subprocess.check_output
함수:
def line_count(filename):
return int(subprocess.check_output(['wc', '-l', filename]).split()[0])
Perflot 분석 후 버퍼링된 읽기 솔루션을 추천해야 함
def buf_count_newlines_gen(fname):
def _make_gen(reader):
while True:
b = reader(2 ** 16)
if not b: break
yield b
with open(fname, "rb") as f:
count = sum(buf.count(b"\n") for buf in _make_gen(f.raw.read))
return count
빠르고 기억력이 좋다.대부분의 다른 해결책들은 약 20배 느리다.
플롯을 재현하는 코드:
import mmap
import subprocess
from functools import partial
import perfplot
def setup(n):
fname = "t.txt"
with open(fname, "w") as f:
for i in range(n):
f.write(str(i) + "\n")
return fname
def for_enumerate(fname):
i = 0
with open(fname) as f:
for i, _ in enumerate(f):
pass
return i + 1
def sum1(fname):
return sum(1 for _ in open(fname))
def mmap_count(fname):
with open(fname, "r+") as f:
buf = mmap.mmap(f.fileno(), 0)
lines = 0
while buf.readline():
lines += 1
return lines
def for_open(fname):
lines = 0
for _ in open(fname):
lines += 1
return lines
def buf_count_newlines(fname):
lines = 0
buf_size = 2 ** 16
with open(fname) as f:
buf = f.read(buf_size)
while buf:
lines += buf.count("\n")
buf = f.read(buf_size)
return lines
def buf_count_newlines_gen(fname):
def _make_gen(reader):
b = reader(2 ** 16)
while b:
yield b
b = reader(2 ** 16)
with open(fname, "rb") as f:
count = sum(buf.count(b"\n") for buf in _make_gen(f.raw.read))
return count
def wc_l(fname):
return int(subprocess.check_output(["wc", "-l", fname]).split()[0])
def sum_partial(fname):
with open(fname) as f:
count = sum(x.count("\n") for x in iter(partial(f.read, 2 ** 16), ""))
return count
def read_count(fname):
return open(fname).read().count("\n")
b = perfplot.bench(
setup=setup,
kernels=[
for_enumerate,
sum1,
mmap_count,
for_open,
wc_l,
buf_count_newlines,
buf_count_newlines_gen,
sum_partial,
read_count,
],
n_range=[2 ** k for k in range(27)],
xlabel="num lines",
)
b.save("out.png")
b.show()
Python의 파일 오브젝트 메서드를 사용할 경우readlines
, 다음과 같다.
with open(input_file) as foo:
lines = len(foo.readlines())
이렇게 하면 파일이 열리고 파일에 줄 목록이 작성되며 목록의 길이를 세어 변수에 저장하고 파일을 다시 닫는다.
이것은 내가 순수한 비단뱀을 사용한 것 중에서 가장 빠른 것이다.내 컴퓨터에서는 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), ''))
나는 여기서 답을 찾았다. 왜 Python보다 C++에서 stdin의 대사를 읽는 것이 더 느릴까? 그리고 그것을 아주 조금 수정했다.하지만 줄을 빨리 세는 방법을 이해하는 것은 매우 좋은 읽을거리다.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
여기 내가 사용하는 것이 있는데, 꽤 깨끗해 보인다.
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])
UPDATE: 이것은 순수한 비단뱀을 사용하는 것보다 약간 더 빠르지만 메모리 사용량에 대한 비용이다.하위 프로세스는 명령을 실행하는 동안 상위 프로세스와 동일한 메모리 풋프린트로 새 프로세스를 분기한다.
단일 라인 솔루션:
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')
당신은 버퍼 크기를 가지고 놀 수 있고 약간의 향상을 볼 수 있다.
위의 방법을 완료하기 위해 파일 입력 모듈로 변형된 모델을 사용해 보십시오.
import fileinput as fi
def filecount(fname):
for line in fi.input(fname):
pass
return fi.lineno()
그리고 위에 언급된 모든 방법에 6천만 줄의 파일을 전달하였다.
mapcount : 6.1331050396
simplecount : 4.588793993
opcount : 4.42918205261
filecount : 43.2780818939
bufcount : 0.170812129974
파일입력이 그렇게 나쁘고 다른 모든 방법보다 훨씬 더 나쁜 규모라는 것은 내게는 좀 놀라운 일이다...
나로서는 이 변종이 가장 빠를 것이다.
#!/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 제외)도 계산된다.
이 코드는 더 짧고 선명하다.그게 아마 가장 좋은 방법일 거야
num_lines = open('yourfile.ext').read().count('\n')
Linux의 Python에서 라인 카운트를 저렴하게 받고 싶다면 다음 방법을 추천한다.
import os
print os.popen("wc -l file_path").readline().split()[0]
file_path는 추상 파일 경로 또는 상대 경로일 수 있다.이것이 도움이 되기를 바란다.
이미 많은 해답이 있지만 불행히도 대부분은 거의 최적화할 수 없는 문제에 있는 작은 경제일 뿐이다...
라인 카운트가 소프트웨어의 핵심 기능인 몇 가지 프로젝트를 했고, 엄청난 수의 파일을 가지고 가능한 한 빨리 작업하는 것이 무엇보다 중요했다.
라인 개수가 있는 주된 병목현상은 I/O 액세스인데, 라인 리턴 문자를 감지하려면 각 라인을 읽어야 하기 때문에, 도저히 방법이 없다.두 번째 잠재적 병목현상은 메모리 관리다. 즉, 한 번에 로딩할수록 처리 속도가 빨라질 수 있지만, 이 병목현상은 첫 번째 병목현상에 비해 무시할 수 있다.
따라서 gc 수집 비활성화 및 기타 마이크로 매니징 트릭과 같은 작은 최적화 외에도 라인 카운트 기능의 처리 시간을 줄이는 3가지 주요 방법이 있다.
하드웨어 솔루션: 프로그래밍을 하지 않는 가장 확실한 방법은 매우 빠른 SSD/플래시 하드 드라이브를 구입하는 것이다.지금까지, 이것이 당신이 가장 큰 속도 상승을 얻을 수 있는 방법이다.
데이터 준비 솔루션: 처리 중인 파일이 생성되는 방식을 생성하거나 수정할 수 있거나 사전 처리할 수 있는 경우 먼저 라인 리턴을 유닉스 스타일로 변환하십시오(
\n
이렇게 하면 Windows 또는 MacOS 스타일에 비해 1자가 절약되며(큰 절약은 아니지만 쉽게 얻을 수 있음), 두 번째로 중요한 것은 고정된 길이의 줄을 쓸 수 있다는 것이다.가변 길이가 필요하면 항상 더 작은 선을 패딩할 수 있다.이렇게 하면 전체 파일 크기에서 즉시 줄 수를 계산할 수 있으며, 이 숫자는 훨씬 더 빨리 액세스할 수 있다.종종, 문제에 대한 최선의 해결책은 그것이 당신의 최종 목적에 더 잘 맞도록 그것을 미리 처리하는 것이다.병렬화 + 하드웨어 솔루션: 여러 개의 하드 디스크(그리고 가능한 경우 SSD 플래시 디스크)를 구입할 수 있다면 병렬화를 활용함으로써 한 디스크의 속도를 뛰어넘어 디스크 간에 파일을 균형 있게 저장(전체 크기에 따라 균형을 맞추는 것이 가장 쉽다)한 다음 모든 디스크에서 병렬로 읽을 수 있다.그러면, 당신은 당신이 가지고 있는 디스크의 수에 비례하여 더 큰 증가를 기대할 수 있다.디스크를 여러 개 구입하는 것이 선택사항이 아니라면 병렬화는 도움이 되지 않을 것이다(일부 전문가급 디스크와 같은 판독 헤더가 여러 개 있지만, 그렇다고 하더라도 디스크의 내부 캐시 메모리와 PCB 회로는 병목 현상이 되어 모든 헤드를 완전히 병렬로 사용하지 못하게 할 뿐 아니라, 또한 고안해야 한다.정확한 클러스터 매핑을 알아야 다른 헤드의 클러스터에 파일을 저장할 수 있고, 이후 다른 헤드로 파일을 읽을 수 있기 때문에 이 하드 드라이브에 사용할 특정 코드).실제로 순차적 판독은 거의 항상 랜덤 읽기보다 빠르며, 단일 디스크에서 병렬화하면 순차적 판독보다 랜덤 읽기와 유사한 성능을 갖는다는 것은 널리 알려져 있다(예를 들어 CrystalDiskMark를 사용하여 두 가지 측면에서 모두 하드 드라이브 속도를 테스트할 수 있다).
만약 그것들 중 어느 것도 선택사항이 아니라면, 당신은 당신의 라인 카운트 기능의 몇 퍼센트 속도만큼 향상시키기 위해 마이크로 매니징 기술에 의존할 수 있지만, 정말로 중요한 것을 기대하지는 마십시오.오히려, 여러분은 여러분이 보게 될 속도 개선의 수익률에 비해 트위킹을 하는 데 걸리는 시간이 불균형적일 것이라고 예상할 수 있다.
간단한 방법:
1)
>>> f = len(open("myfile.txt").readlines())
>>> f
430
>>> 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
.
이건 어때?
def file_len(fname):
counts = itertools.count()
with open(fname) as f:
for _ in f: counts.next()
return counts.next()
count = max(enumerate(open(filename)))[0]
이건 어때?
import fileinput
import sys
counter=0
for line in fileinput.input([sys.argv[1]]):
counter+=1
fileinput.close()
print counter
이 원라이너 어떠세요?
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
print open('file.txt', 'r').read().count("\n") + 1
def line_count(path):
count = 0
with open(path) as lines:
for count, l in enumerate(lines, start=1):
pass
return count
def count_text_file_lines(path):
with open(path, 'rt') as file:
line_count = sum(1 for _line in file)
return line_count
참조URL: https://stackoverflow.com/questions/845058/how-to-get-line-count-of-a-large-file-cheaply-in-python
'IT이야기' 카테고리의 다른 글
반응JS - 요소의 높이 가져오기 (0) | 2022.04.09 |
---|---|
vue.js 애플리케이션의 상태 업데이트에 사용할 항목(rxJs 대 Vuex) (0) | 2022.04.09 |
Python 3의 상대적 가져오기 (0) | 2022.04.09 |
RxJS 구독() 함수의 구성요소로 선언된 액세스 변수 (0) | 2022.04.09 |
환원 관측 가능에서 여러 작업을 전송하는 방법? (0) | 2022.04.09 |