Python readlines() использование и эффективная практика для чтения

у меня проблема с разбором 1000 текстовых файлов(около 3000 строк в каждом файле размером ~400 КБ ) в папке. Я читал их, используя readlines,

   for filename in os.listdir (input_dir) :
       if filename.endswith(".gz"):
          f = gzip.open(file, 'rb')
       else:
          f = open(file, 'rb')

       file_content = f.readlines()
       f.close()
   len_file = len(file_content)
   while i < len_file:
       line = file_content[i].split(delimiter) 
       ... my logic ...  
       i += 1  

это отлично работает для выборки из моих входных данных (50,100 файлов) . Когда я запускал на весь вход более 5K файлов, время не было близко к линейному приращению.Я планировал сделать анализ производительности и сделал анализ Cprofile. Время, затраченное на большее количество файлов в экспоненциальном увеличении с достижение худших темпов, когда входы достигли 7K файлов.

вот совокупное время, затраченное на чтение строк , во-первых - > 354 файлов (образец от входного сигнала) и второй - > 7473 файлов (весь вход)

 ncalls  tottime  percall  cumtime  percall filename:lineno(function)
 354    0.192    0.001    **0.192**    0.001 {method 'readlines' of 'file' objects}
 7473 1329.380    0.178  **1329.380**    0.178 {method 'readlines' of 'file' objects}

из-за этого время, затраченное моим кодом, не линейно масштабируется по мере увеличения ввода. Я читал некоторые заметки doc на readlines(), где люди утверждали, что это readlines() считывает все содержимое файла в память и, следовательно, как правило, потребляет больше памяти по сравнению с readline() или read().

Я согласен с этим пунктом ,но должен ли сборщик мусора автоматически очищать загруженный контент из памяти в конце моего цикла, поэтому в любой момент моя память должна иметь только содержимое моего текущего обработанного файла? Но здесь есть подвох. Может кто-нибудь дать некоторое представление об этой проблеме.

является ли это врожденным поведением readlines() или моя неправильная интерпретация сборщика мусора python. Рад знать.

кроме того, предложить некоторые альтернативные способы сделать то же самое в памяти и времени эффективным способом. ТИА.

2 ответов


краткая версия:эффективный способ использовать readlines() не использовать его. Когда-либо.


я читал некоторые заметки doc на readlines(), где люди утверждали, что это readlines() читает все содержимое файла в память и, следовательно, обычно потребляет больше памяти по сравнению с readline () или read ().

документация readlines() гарантирует что он читает весь файл в память, и разбирает ее на строки, и строит list полное strings из этих строк.

но документация для read() также гарантирует, что он считывает весь файл в память и создает string, так что это не помогает.


помимо использования большего объема памяти, это также означает, что вы не можете работать, пока все это не будет прочитано. Если вы чередуете чтение и обработку даже самым наивным способом, вы выиграете по крайней мере, из некоторого конвейера (благодаря дисковому кэшу ОС, DMA, конвейеру CPU и т. д.), поэтому вы будете работать над одной партией, пока читается следующая партия. Но если вы заставите компьютер прочитать весь файл, а затем разобрать весь файл, а затем запустить код, вы получите только одну область перекрывающейся работы для всего файла вместо одной области перекрывающейся работы на чтение.


вы можете обойти это тремя способами:

  1. написать цикл вокруг readlines(sizehint), read(size) или readline().
  2. просто используйте файл в качестве ленивого итератора без вызова любого из них.
  3. 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 использует refcounting для GC, что означает, что в вашем коде, как только file_content получает отскок или уходит, гигантский список строк, и все строки в нем, будут освобождены к списку freelist, то же память может быть повторно использованы снова для вашего следующего прохода.

однако все эти распределения, копии и освобождения не бесплатны-гораздо быстрее не делать их, чем делать их.

на кроме того, наличие ваших строк, разбросанных по большой полосе памяти, вместо повторного использования одного и того же небольшого куска памяти снова и снова вредит вашему поведению кэша.

плюс, хотя использование памяти может быть постоянным (или, скорее, линейным по размеру вашего самого большого файла, а не в сумме ваших размеров файлов), этот порыв mallocs, чтобы расширить его в первый раз, будет одной из самых медленных вещей, которые вы делаете (что также значительно затрудняет выполнение сравнения.)


собирая все это вместе, вот как я бы написал вашу программу:

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 ...

читать строку за строкой, а не весь файл:

for line in open(file_name, 'rb'):
    # process line here

еще лучше использовать with для автоматического закрытия файла:

with open(file_name, 'rb') as f:
    for line in f:
        # process line here

выше будет читать объект файла с помощью итератора, по одной строке за раз.