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

У меня есть несколько больших (~ 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?

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


person SilentGhost    schedule 04.02.2009    source источник
comment
Что в файле? Что делает обработка?   -  person Torsten Marek    schedule 04.02.2009
comment
временные ряды, разделенные запятыми, которые я сокращаю до некоторых понятных итоговых переменных   -  person SilentGhost    schedule 04.02.2009
comment
Не могли бы вы быть более конкретными, может быть, разместите небольшой пример?   -  person oefe    schedule 05.02.2009
comment
scikits.timeseries.lib.moving_funcs содержит функции движущегося окна, которые позволяют использовать фиксированный объем памяти (только текущий диапазон) для произвольных больших данных.   -  person jfs    schedule 05.02.2009
comment
pytseries.sourceforge.net/lib/moving_funcs.html   -  person jfs    schedule 05.02.2009
comment
@jfs, спасибо, я посмотрю на pytseries (особенно когда они будут поддерживать py3k), похоже, именно то, что мне нужно   -  person SilentGhost    schedule 05.02.2009


Ответы (6)


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

person Ryan Ginstrom    schedule 05.02.2009
comment
Угу, как только я увидел вопрос, я вскочил, чтобы ответить со ссылкой на материал Бизли, и увидел, что вы уже дали его как ответ. Ну что ж, вместо этого придется проголосовать за вас +1! Хотел бы я дать ему больше +1. - person Van Gale; 12.02.2009

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

person Crashworks    schedule 04.02.2009
comment
100 Мб - это нормально, проблема начинается, когда он достигает 1,7 Гб виртуальной памяти. - person SilentGhost; 04.02.2009
comment
Ой! Это больше похоже на то, что вы цепляетесь за ссылки на многие вещи, так что сборщик мусора не может их очистить. Это может произойти, если вы сохраните ссылку на свои промежуточные данные в классе обработки. - person Crashworks; 04.02.2009

Не читайте файл размером 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(' ')))
person Sam Corder    schedule 04.02.2009
comment
похоже, он не масштабируется с точки зрения кода, мне не нужно просто переставлять биты, требуется дополнительная обработка - person SilentGhost; 04.02.2009
comment
После того, как вы проанализировали линию деталей и выполнили вычисления сокращения, убедитесь, что вы не цепляетесь ни за один из объектов, созданных в результате анализа деталей. Python GC основан на справочниках. Пока есть ссылка на объект, он не будет обработан сборщиком мусора. - person Sam Corder; 05.02.2009
comment
Просто добавить. Если у вас есть два объекта, которые ссылаются друг на друга, они никогда не будут собираться мусором, если один из них не отпустит ссылку на другой. Проверьте этот вид циклической ссылки, если вы видите, что использование памяти увеличивается и объекты должны быть вне области видимости. - person Sam Corder; 05.02.2009
comment
@Sam Corder: Циклическая сборка мусора давно добавлена ​​в Python. - person Torsten Marek; 05.02.2009

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

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 не увеличивается со временем.

  1. Обеспечьте соответствующие реализации initial, parse и combine
  2. Не читайте файл побайтово, а читайте в фиксированном буфере, выполняйте синтаксический анализ из буфера и читайте больше по мере необходимости.
  3. Это в основном то, что делает встроенная функция reduce, но для ясности здесь я использовал явный цикл for. Вот то же самое с reduce:

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

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

person Torsten Marek    schedule 04.02.2009
comment
извините, если я выразился коряво, но под уменьшением я имел в виду сделать 32 Кбайт из 100 Мбайт - person SilentGhost; 04.02.2009
comment
Нет, я не это имел в виду, я имел в виду встроенное сокращение. - person Torsten Marek; 04.02.2009
comment
кстати, f.read() должно быть f.read(1) в вашем коде. И open (somefile, r) - ›open (somefile, r). - person jfs; 05.02.2009
comment
@ J.F .: Ах, радости программирования без тестирования. Я действительно пробовал код и использовал там f.read (1). - person Torsten Marek; 05.02.2009

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

Похоже, настоящая проблема, с которой вы столкнулись, вовсе не с чтением файла, а со структурами данных, которые вы выделяете при обработке файлов. Кондирование с использованием 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]) для чтения блоков файла. Однако обычно вы делаете это для увеличения производительности ЦП за счет многопоточности обрабатывающей части вашего скрипта, чтобы вы могли читать и обрабатывать одновременно. Но это не помогает с использованием памяти; на самом деле он использует больше памяти.

Другой вариант - mmap, который выглядит так:

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

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

person user26294    schedule 05.02.2009

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

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

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

person nosklo    schedule 12.02.2009