Использование памяти при обработке/обработке больших данных

У меня есть несколько больших (~ 100 Мб) файлов, которые я регулярно обрабатываю. Хотя я пытаюсь удалить ненужные структуры данных во время обработки, потребление памяти слишком велико. Мне было интересно, есть ли способ эффективно манипулировать большими данными, например:

def read(self, filename):
    fc = read_100_mb_file(filename)
    self.process(fc)
def process(self, content):
    # do some processing of file content

Есть ли дублирование структур данных? Разве не лучше использовать память для использования атрибута класса, такого как self.fc?

Когда я должен использовать сбор мусора? Я знаю о модуле gc, но могу ли я его вызвать после я del fc например?

Обновление
постскриптум 100 Мб не проблема сама по себе. но конвертирование с плавающей точкой, дальнейшая обработка значительно увеличивает как рабочий набор, так и виртуальный размер (я нахожусь в Windows).

Ответы

Ответ 1

Я бы предложил посмотреть презентацию Дэвида Бизли об использовании генераторов в Python. Этот метод позволяет обрабатывать множество данных и выполнять сложную обработку, быстро и без взрыва использования вашей памяти. ИМО, трюк не содержит огромное количество данных в памяти как можно более эффективно; трюк позволяет одновременно загружать огромное количество данных в память.

Ответ 2

Прежде чем вы начнете разрывать волосы над сборщиком мусора, вы можете избежать этого 100-мегабайтного удара по загрузке всего файла в память с помощью файла с отображением памяти. См. Модуль mmap.

Ответ 3

Не читайте весь 100-мегабайтный файл за раз. Используйте потоки для обработки немного за раз. Проверьте это сообщение в блоге, в котором говорится о работе с большими файлами csv и xml. http://lethain.com/entry/2009/jan/22/handling-very-large-csv-and-xml-files-in-python/

Вот пример кода из статьи.

from __future__ import with_statement # for python 2.5

with open('data.in','r') as fin:
    with open('data.out','w') as fout:
        for line in fin:
            fout.write(','.join(line.split(' ')))

Ответ 4

Итак, из ваших комментариев я предполагаю, что ваш файл выглядит примерно так:

item1,item2,item3,item4,item5,item6,item7,...,itemn

который вы все сводите к одному значению путем повторного применения некоторой комбинированной функции. В качестве решения читайте только одно значение за раз:

def read_values(f):
    buf = []
    while True:
        c = f.read(1)
        if c == ",":
            yield parse("".join(buf))
            buf = []
        elif c == "":
            yield parse("".join(buf))
            return
        else:
            buf.append(c)

with open("some_file", "r") as f:
     agg = initial
     for v in read_values(f):
         agg = combine(agg, v)

Таким образом, потребление памяти остается постоянным, если agg не растет во времени.

  • Обеспечьте соответствующие реализации initial, parse и combine
  • Не читайте файл побайтно, но читайте в фиксированном буфере, разбирайте его из буфера и читайте больше, как вам это нужно.
  • Это в основном то, что делает встроенная функция reduce, но я для ясности использовал явный цикл для этого. Здесь же с помощью reduce:

    with open("some_file", "r") as f:
        agg = reduce(combine, read_values(f), initial)
    

Надеюсь, я правильно истолковал вашу проблему.

Ответ 5

Прежде всего, не трогайте сборщик мусора. Это не проблема, не решение.

Похоже, что реальная проблема, с которой вы сталкиваетесь, связана не с чтением файла, а с структурами данных, которые вы выделяете при обработке файлов. Сжатие с помощью del для удаления структур, которые вам больше не нужны во время обработки. Кроме того, вы можете использовать marshal, чтобы выгрузить часть обработанных данных на диск во время работы через следующие 100 МБ входных файлов.

Для чтения файлов у вас есть в основном два варианта: файлы в стиле unix как потоки или файлы с отображением памяти. Для файлов с потоками файл python по умолчанию уже буферизирован, поэтому самый простой код также, вероятно, наиболее эффективен:

  with open("filename", "r") as f:
    for line in f:
       # do something with a line of the files

В качестве альтернативы вы можете использовать f.read([size]) для чтения блоков файла. Однако обычно вы делаете это, чтобы повысить производительность процессора, многопоточно обрабатывая часть обработки вашего script, чтобы вы могли читать и обрабатывать одновременно. Но это не помогает при использовании памяти; на самом деле он использует больше памяти.

Другой вариант - mmap, который выглядит следующим образом:

  with open("filename", "r+") as f:
    map = mmap.mmap(f.fileno(), 0)
    line = map.readline()
    while line != '':
       # process a line
       line = map.readline()

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

Ответ 6

В вашем примере кода данные хранятся в переменной fc. Если вы не сохраните ссылку на fc, все содержимое вашего файла будет удалено из памяти, когда метод read завершится.

Если это не так, то вы сохраняете ссылку где-нибудь. Возможно, ссылка создается в read_100_mb_file, возможно, в process. Если ссылок нет, реализация CPython будет отменена почти сразу.

Есть несколько инструментов, которые помогут вам найти, где эта ссылка, guppy, dowser, pysizer...