Как дешево подсчитать количество строк в большом файле на Python
Как получить количество строк в большом файле наиболее эффективным с точки зрения памяти и времени образом?
У меня есть функция для подсчета строк в файле:
def file_len(filename):
with open(filename) as f:
for i, _ in enumerate(f):
pass
return i + 1
Хотелось бы узнать, есть ли более оптимальные способы добиться этой же цели. Рассматриваю как использование меньшего количества памяти, так и сокращение времени выполнения. Буду признателен за советы и оптимизации!
5 ответ(ов)
Вы не найдете ничего лучше этого.
В конце концов, любое решение должно будет прочитать весь файл, выяснить, сколько у вас символов \n
, и вернуть этот результат.
Есть ли у вас более эффективный способ сделать это без чтения всего файла? Не уверен... Лучше всего оптимизированные решения всегда будут зависеть от ввода-вывода; лучшее, что вы можете сделать, это убедиться, что не используете лишнюю память, но, похоже, вы уже это учли.
[Правка май 2023]
Как комментировалось в многих других ответах, в Python 3 есть более эффективные альтернативы. Цикл for
не является самым производительным решением. Например, использование mmap
или буферов будет более эффективным.
Все предложенные решения игнорируют способ значительно ускорить выполнение, а именно использование необработанного (сыро) интерфейса, применение bytearrays и создание собственного буферизованного чтения. (Это касается только Python 3. В Python 2 необработанный интерфейс может использоваться по умолчанию, но в Python 3 вы начинаете работать с Unicode.)
Используя модифицированную версию инструмента для замера времени, я пришел к выводу, что следующий код работает быстрее (и немного более "питонично"), чем любое из предложенных решений:
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
Используя отдельную генераторную функцию, этот вариант работает немного быстрее:
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)
Вот мои замеры времени:
функция среднее, с мин, с соотношение
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
Эти результаты показывают, что использование необработанного интерфейса и буферизация очень эффективно при подсчете строк в файлах.
Вы можете использовать модуль subprocess
для выполнения команды и подсчета строк в файле с помощью 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])
В этой функции создается новый процесс, который выполняет команду wc -l
для указанного файла. Результат команды и возможные ошибки захватываются с помощью stdout
и stderr
. Если выполнение команды завершилось с ошибкой (то есть returncode
не равен 0), возникает исключение IOError
с текстом ошибки. В противном случае функция возвращает число строк в файле, которое извлекается из результата выполнения команды.
Вот программа на Python, использующая библиотеку multiprocessing для распределения подсчета строк по машинам/ядрам. Мой тест показывает, что подсчет строк в файле на 20 миллионов строк занимает 26 секунд, а с помощью этой программы — всего 7 секунд на сервере Windows 64-bit с 8 ядрами. Обратите внимание: если не использовать отображение памяти, процесс значительно замедляется.
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() # Новый логгер на корневом уровне
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('начало')
physical_file = open(file1, "r")
# mmap.mmap(fileno, length[, tagname[, access[, offset]]]
m1 = mmap.mmap(physical_file.fileno(), 0, access=mmap.ACCESS_READ)
# Определяем размер файла для распределения подсчета строк
fSize = os.stat(file1).st_size
chunk = (fSize / processes) + 1
lines = 0
# Определяем, откуда начинать и где заканчивать
_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
# Находим, с какой позиции начинать
if pid > 0:
m1.seek(seekStart)
# Читаем следующую строку
l1 = m1.readline() # Используем readline с отображаемыми файлами
seekStart = m1.tell()
# Сообщаем предыдущему процессу, с какой позиции начинать, чтобы тот знал, где заканчивать
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('завершено')
# Подсчитываем результаты
if pid == 0:
for p in range(1, processes):
lines += queues[0].get()
queues[0].put(lines) # Общее количество строк
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('необходимы параметры: имя файла [число процессов]')
exit()
t = time.time()
processes = multiprocessing.cpu_count()
if len(sys.argv) > 2:
processes = int(sys.argv[2])
queues = [] # Очередь для каждого процесса
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() # Ждем завершения подсчета
lines = queues[0].get()
logging.info('завершено {} Lines:{}'.format(time.time() - t, lines))
Этот код демонстрирует, как можно эффективно использовать многопоточность для ускорения обработки больших файлов, особенно при использовании mmap для улучшения производительности.
Я бы использовал метод readlines
объектов файлов в Python следующим образом:
with open(input_file) as foo:
lines = len(foo.readlines())
Этот код открывает файл, создает список строк из содержимого файла, подсчитывает количество строк в списке, сохраняет это значение в переменную и автоматически закрывает файл.
Как изменить порядок столбцов в DataFrame?
Получение текущей даты в формате YYYY-MM-DD в Python
Как заполнить строку в Python пробелами?
Как удалить пакеты, установленные с помощью easy_install в Python?
Использование @property против геттеров и сеттеров