Python: сокращение использования памяти словаря
Я пытаюсь загрузить пару файлов в память. Файлы имеют следующие форматы 3:
- строка TAB int
- строка TAB float
- int TAB float.
действительно, они являются статическими файлами ngram, если это помогает с решением. Например:
i_love TAB 10
love_you TAB 12
в настоящее время псевдокод я делаю прямо сейчас
loadData(file):
data = {}
for line in file:
first, second = line.split('t')
data[first] = int(second) #or float(second)
return data
к моему удивлению, в то время как общий размер файлы на диске составляет около 21 Мб, при загрузке в память процесс занимает 120 - 180 МБ памяти! (все приложение python не загружает никакие другие данные в память).
есть менее 10 файлов, большинство из них будет оставаться стабильным на уровне 50-80k строк, за исключением одного файла, который в настоящее время имеет миллионы строк.
поэтому я хотел бы попросить техника/структуры данных, чтобы уменьшить потребление памяти:
- любые советы по сжатию техник?
- если я все еще использую dict, есть ли способ уменьшить память? Можно ли установить "коэффициент загрузки", как в Java для Python dict?
- если у вас есть некоторые другие структуры данных, я также готов торговать некоторой скоростью, чтобы уменьшить память. Тем не менее, это чувствительное ко времени приложение, так что, как только пользователи вводят свои запросы, я думаю, было бы не совсем разумно занимать больше нескольких секунд, чтобы вернуть результат. Что касается этого, я все еще поражен тем, как Google удается сделать Google Translate так быстро: они должны использовать много методов + много мощности серверов?
большое спасибо. С нетерпением жду вашего совета.
6 ответов
Я не могу предложить полную стратегию, которая помогла бы улучшить объем памяти, но я считаю, что это может помочь проанализировать, что именно занимает так много памяти.
Если вы посмотрите на реализация Python словаря (который является относительно прямой реализацией хэш-таблицы), а также реализацией встроенных строковых и целочисленных типов данных, например здесь (конкретно объект.h, intobject.h, stringobject.h и диктопроект.h, а также соответствующий *.с файлы ../ Objects), вы можете с некоторой точностью рассчитать ожидаемые требования к пространству:
An целое - объект фиксированного размера, т. е. он содержит счетчик ссылок, указатель типа и фактическое целое число, в общей сложности обычно по крайней мере, 12 байт на 32-битной системе и 24 байт на 64-битной системе, не принимая во внимание дополнительное пространство, возможно, потерянное через выравнивание.
-
A строка объект имеет переменный размер, что означает, что он содержит
- счетчик ссылок
- указатель типа
- информация размере
- пробел для лениво вычисленного хэш-кода
- информация о состоянии (например, используется для интернированы строки)
- указатель на динамический контент
в общей сумме по крайней мере 24 байта на 32bit или 60 байт на 64bit,не включая пространство для самой строки.
-
на словарь сам состоит из нескольких ведер, каждое из которых содержит
- хэш-код объекта, хранящегося в данный момент (что не предсказуемо с позиции ведра из-за используемой стратегии разрешения столкновений)
- указатель на ключевой объект
- указатель на значение объект
в общей сумме по крайней мере, 12 байт на 32bit и 24 байт на 64bit.
словарь начинается с 8 пустых ведер и изменение размера путем удвоения количество записей при достижении его емкости.
Я провел тест со списком 46,461 уникальных строк (337,670 байт объединенный размер строки), каждый из которых связан с integer-аналогично вашей настройке на 32-разрядной машине. Согласно расчетам выше, я ожидал бы минимальный объем памяти
- 46,461 * (24+12) bytes = 1.6 MB для комбинаций string/integer
- 337,670 = 0,3 МБ для содержимого строки
- 65,536 * 12 байт = 1,6 Мб для хэш-ведер (после изменения размера в 13 раз)
всего 2,65 МБ. (Для 64-разрядной системы соответствующий расчет дает 5.5 МЕГАБАЙТ.)
при запуске интерпретатора Python в режиме ожидания его след в соответствии с ps
-инструмент составляет 4,6 МБ. Таким образом, общее ожидаемое потребление памяти после создания словаря составляет около 4,6 + 2.65 = 7.25 МБ. на истинный след памяти (по данным ps
) в моем тесте был 7.6 МБ. Я думаю, что дополнительный ca. 0.35 MB были потреблены накладными расходами, сгенерированными через стратегию выделения памяти Python (для арен памяти так далее.)
конечно, многие люди теперь укажут, что мое использование ps
для измерения объема памяти неточно, и мои предположения о размере типов указателей и целых чисел в 32-разрядных и 64-разрядных системах могут быть неправильными во многих конкретных системах. Предоставленный.
но, тем не менее,основные выводы:, Я полагаю, это:
- Питон реализация словарь потребляет удивительно маленький объем памяти
- но возможностей много int и (в частности) string объекты, для отсчетов ссылки, pre-высчитанных кодов etc., это больше, чем вы думаете сначала
- здесь вряд ли способ избежать памяти, пока вы используете Python и хотите, чтобы строки и целые числа представлялись как отдельные объекты - по крайней мере, я не вижу, как это можно сделать
- возможно, стоит ищите (или реализуйте себя) a расширение Python-C это реализует хэш, который хранит ключи и значения как C-указатели (а не объекты Python). Я не знаю, существует ли это; но я верю, что это может быть сделано и может уменьшить объем памяти более чем наполовину.
1) SQLite в памяти звучит как отличное решение, это позволит вам запросить ваши данные более легко, как только он загружен, что приятно
и sqlite3.connect (': memory:')
2) вам, вероятно, нужен именованный кортеж - я уверен, что они легче словарей, и вы можете получить доступ к свойствам с помощью точечной нотации (для которой у меня есть эстетическое предпочтение).
http://docs.python.org/dev/library/collections
3) Вы можете хотите взглянуть на Redis: https://github.com/andymccurdy/redis-py
это быстро и позволит вам легко сохранять вещи, то есть вам не нужно загружать весь набор каждый раз, когда вы хотите его использовать.
4) trie звучит как хорошая идея, но добавляет некоторую теоретическую сложность на вашем конце работы. Вы можете использовать Redis для его реализации и хранения, что еще больше повысит вашу скорость.
но в целом, кортежей, вероятно, трюк здесь.
на диске у вас есть только строки, При загрузке на Python интерпретатор должен создать целую структуру для каждой строки и для каждого словаря, кроме самой строки.
нет никакого способа, чтобы уменьшить объем памяти, используемый предсказывает, но есть и другие способы решения проблемы. Если вы хотите обменять некоторую скорость на память, вы должны рассмотреть возможность хранения и запроса строк из файла SQLite вместо загрузки всего в словари в памяти.
звучит как Trie (http://en.m.wikipedia.org/wiki/Trie) структура данных может лучше соответствовать вашему желанию эффективности памяти.
Update: эффективность памяти python dict была поднята как проблема, хотя она была отклонена от стандартного lib, учитывая доступность сторонних библиотек. Смотри:http://bugs.python.org/issue9520
вы можете заменить dict на блист.sorteddict для доступа к журналу (n) без накладных расходов памяти. Это удобно, потому что он ведет себя точно так же, как словарь, т. е. он реализует все свои методы, поэтому вам нужно только изменить начальный тип.
Если вы пытаетесь компактно хранить числовые данные в python в памяти, ваше лучшее решение, вероятно, Numpy.
Numpy (http://numpy.org) выделяет структуры данных, используя собственные структуры C. Большинство его структур данных предполагают, что вы храните один тип данных, поэтому это не для всех ситуаций(вам может потребоваться сохранить null и т. д.), но это может быть очень, очень, очень быстро и примерно так компактно, как вы могли бы попросить. Много труда с ним (см. Также составляющей).
конечно, есть еще один вариант:zlib, Если у вас есть:
- достаточно циклов процессора и
- много данных, которые не будут вписываться в память
вы можете просто объявить "страницу" данных (какой бы большой вы ни хотели) в виде массива, прочитать данные, сохранить их в массиве, zip, а затем прочитать еще несколько данных, пока у вас не будет всех данных в памяти вы хотите.
затем повторите страницы, распакуйте, преобразуйте обратно в массив и выполняйте свои операции по мере необходимости. Например:
def arrayToBlob(self, inArray):
a = array.array('f', inArray)
return a.tostring()
def blobToArray(self, blob, suppressWarning=False):
try:
out = array.array('f', [])
out.fromstring(blob)
except Exception, e:
if not suppressWarning:
msg = "Exception: blob2array, err: %s, in: %s" % (e, blob)
self.log.warning(msg)
raise Exception, msg
return out
Как только у вас есть данные в виде blob, вы можете передать этот blob в zlib и сжатия данных. Если у вас много повторяющихся значений, этот blob может быть значительно сжат.
конечно, это медленнее, чем держать все это несжатым, но если вы не можете поместить его в память, ваш выбор ограничен.
даже при сжатии это может быть не все поместитесь в память, и тогда вам, возможно, придется записать сжатые страницы в виде строк или солений и т. д.
удачи!