Ответ 1
Короткий вариант: Эффективный способ использования readlines()
- не использовать его. Когда-либо.
Я прочитал некоторые примечания к doc на
readlines()
, где люди утверждали, что этотreadlines()
читает весь файл в памяти и, следовательно, обычно потребляет больше памяти по сравнению с readline() или read().
Документация для readlines()
явно гарантирует, что она считывает весь файл в память и анализирует его в строках и создает list
полный str
из этих строк.
Но документация для read()
также гарантирует, что она считывает весь файл в память и создает str
ing, так что не помогает.
Помимо использования большего количества памяти, это также означает, что вы не можете выполнять какую-либо работу, пока все это не будет прочитано. Если вы будете чередоваться с чтением и обработкой даже самым наивным образом, вы получите хотя бы некоторую конвейерную поддержку (благодаря дисковым кэшам ОС, DMA, конвейерам CPU и т.д.), Поэтому вы будете работать над одной партией, а следующая партия читается. Но если вы вынудите компьютер прочитать весь файл, а затем проанализируйте весь файл, а затем запустите свой код, вы получите только одну область перекрывающейся работы для всего файла, а не одну область перекрывающейся работы за чтение.
Вы можете обойти это тремя способами:
- Напишите цикл вокруг
readlines(sizehint)
,read(size)
илиreadline()
. - Просто используйте файл как ленивый итератор, не вызывая никаких из них.
-
mmap
файл, который позволяет вам рассматривать его как гигантскую строку без предварительного ее чтения.
Например, это должно сразу прочитать все foo
:
with open('foo') as f:
lines = f.readlines()
for line in lines:
pass
Но это только чтение примерно 8K за раз:
with open('foo') as f:
while True:
lines = f.readlines(8192)
if not lines:
break
for line in lines:
pass
И это только чтение по одной строке за раз, хотя Python разрешено (и будет) выбирать хороший размер буфера, чтобы ускорить выполнение.
with open('foo') as f:
while True:
line = f.readline()
if not line:
break
pass
И это будет делать то же самое, что и предыдущее:
with open('foo') as f:
for line in f:
pass
Тем:
но если сборщик мусора автоматически очистит загруженный контент из памяти в конце моего цикла, значит, в любой момент моя память должна иметь только содержимое моего текущего обработанного файла?
Python не дает никаких гарантий относительно сбора мусора.
В реализации CPython используется пересчет для GC, что означает, что в вашем коде, как только file_content
будет отскакивать или уходить, гигантский список строк и все строки внутри него будут освобождены до freelist, означающий, что одна и та же память может быть повторно использована для вашего следующего прохода.
Однако все эти распределения, копии и освобождения не являются бесплатными - гораздо быстрее не выполнять их, чем выполнять их.
Кроме того, ваши строки, разбросанные по большому объему памяти, вместо повторного использования одного и того же маленького фрагмента памяти снова и снова вредят вашему поведению кэша.
Плюс, в то время как использование памяти может быть постоянным (или, скорее, линейным размером вашего самого большого файла, а не суммой размеров вашего файла), этот прилив malloc
для его расширения в первый раз быть одной из самых медленных вещей, которые вы делаете (что также значительно усложняет выполнение сравнений производительности).
Объединяя все вместе, вот как я напишу вашу программу:
for filename in os.listdir(input_dir):
with open(filename, 'rb') as f:
if filename.endswith(".gz"):
f = gzip.open(fileobj=f)
words = (line.split(delimiter) for line in f)
... my logic ...
Или, может быть:
for filename in os.listdir(input_dir):
if filename.endswith(".gz"):
f = gzip.open(filename, 'rb')
else:
f = open(filename, 'rb')
with contextlib.closing(f):
words = (line.split(delimiter) for line in f)
... my logic ...