Python советы по оптимизации памяти - PullRequest
10 голосов
/ 11 июня 2010

Мне нужно оптимизировать использование оперативной памяти моего приложения.
ПОЖАЛУЙСТА, избавьте меня от лекций о том, что я не должен заботиться о памяти при кодировании Python У меня проблема с памятью, потому что я использую очень большие словари по умолчанию (да, я тоже хочу быть быстрым). Мое текущее потребление памяти составляет 350 МБ и растет. Я уже не могу использовать виртуальный хостинг, и если мой Apache открывает больше процессов, память удваивается и утраивается ... и это дорого.
Я сделал расширенное профилирование и я точно знаю, где мои проблемы.
У меня есть несколько больших (> 100 тысяч записей) словарей с ключами Unicode. Словарь начинается с 140 байт и быстро растет, но большая проблема - ключи. Python оптимизирует строки в памяти (или, как я уже читал), так что поиск может быть сравнением идентификаторов («интернирование» их). Не уверен, что это также верно для строк в Юникоде (я не смог их "интернировать").
Объекты, хранящиеся в словаре, являются списками кортежей (an_object, int, int).

my_big_dict [some_unicode_string] .append ((my_object, an_int, another_int))

Я уже обнаружил, что стоит разделить на несколько словарей, потому что кортежи занимают много места ...
Я обнаружил, что могу сэкономить ОЗУ, хешируя строки, прежде чем использовать их в качестве ключей! Но затем, к сожалению, я столкнулся с днем ​​рождения на моей 32-битной системе. (дополнительный вопрос: есть ли словарь 64-битных ключей, который я могу использовать в 32-битной системе?)

Python 2.6.5 как для Linux (производство), так и для Windows. Любые советы по оптимизации использования памяти словарей / списков / кортежей? Я даже думал об использовании C - мне все равно, если этот маленький кусочек кода уродлив. Это просто уникальное место.

Заранее спасибо!

Ответы [ 7 ]

11 голосов
/ 10 ноября 2010

Я предлагаю следующее: сохранить все значения в БД и сохранить словарь в памяти со строковыми хешами в качестве ключей. Если происходит коллизия, извлекайте значения из БД, в противном случае (в большинстве случаев) используйте словарь По сути, это будет гигантский кеш.

Проблема со словарями в Python заключается в том, что они используют много места: даже словарь int-int использует 45-80 байт на пару ключ-значение в 32-разрядной системе. В то же время array.array('i') использует только 8 байт на пару целых чисел, и при небольшом учете можно реализовать достаточно быструю основанную на массиве int → int словарь.

Если у вас есть эффективная для памяти реализация словаря int-int, разбейте свой словарь string → (object, int, int) на три словаря и используйте хеши вместо полных строк. Вы получите один int → object и два int → int словари. Эмулируйте словарь int → object следующим образом: сохраняйте список объектов и сохраняйте индексы объектов в качестве значений словаря int → int .

Я понимаю, что для получения словаря на основе массива требуется значительное количество кода. У меня была проблема, похожая на вашу, и я реализовал достаточно быстрый, очень эффективный для памяти, универсальный словарь hash-int. Вот мой код (лицензия BSD). Он основан на массиве (8 байт на пару), он заботится о хешировании ключей и проверке коллизий, поддерживает порядок упорядочения массива (на самом деле несколько меньших массивов) во время записи и выполняет двоичный поиск при чтении. Ваш код сводится к чему-то вроде:

dictionary = HashIntDict(checking = HashIntDict.CHK_SHOUTING)
# ...
database.store(k, v)
try:
    dictionary[k] = v
except CollisionError:
    pass
# ...
try:
    v = dictionary[k]
except CollisionError:
    v = database.fetch(k)

Параметр checking указывает, что происходит при столкновении: CHK_SHOUTING повышает CollisionError при чтении и записи, CHK_DELETING возвращает None при чтении и сохраняет молчание при записи, CHK_IGNORING не делает проверка столкновений.

Ниже приведено краткое описание моей реализации, советы по оптимизации приветствуются! Структура данных верхнего уровня представляет собой обычный словарь массивов. Каждый массив содержит до 2^16 = 65536 целочисленных пар (квадратный корень из 2^32). Ключ k и соответствующее значение v оба хранятся в k/65536 -ом массиве. Массивы инициализируются по запросу и хранятся в порядке ключей. Бинарный поиск выполняется при каждом чтении и записи. Проверка на столкновение является опцией. Если включено, попытка перезаписать уже существующий ключ удалит ключ и соответствующее значение из словаря, добавит ключ к набору сталкивающихся ключей и (опять же, опционально) вызовет исключение.

4 голосов
/ 11 июня 2010

Для веб-приложения вы должны использовать базу данных, то, как вы это делаете, вы создаете одну копию вашего dict для каждого процесса apache, что крайне расточительно. Если у вас достаточно памяти на сервере, таблица базы данных будет кэшироваться в памяти (если вам не хватает одной копии вашей таблицы, поместите больше оперативной памяти на сервер). Просто не забудьте поместить правильные индексы в таблицу базы данных, иначе вы получите плохую производительность.

2 голосов
/ 11 июня 2010

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

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

1 голос
/ 11 июня 2010

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

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

Кроме того, у вас естьвариант прокси вашего приложения за nginx вместо использования Apache?Вы можете найти (если это разрешено) это расположение прокси / веб-приложений менее требовательным к ресурсам.

Удачи.

1 голос
/ 11 июня 2010

Если вы хотите остаться с хранилищем данных в памяти, вы можете попробовать что-то вроде memcached .

Таким образом, вы можете использовать один ключ / значение в памятихранить из всех процессов Python.

Существует несколько клиентских библиотек python memcached.

1 голос
/ 11 июня 2010

Используйте shelve или базу данных для хранения данных вместо указания в памяти.

0 голосов
/ 11 июня 2010

Если вы хотите провести обширную оптимизацию и иметь полный контроль над использованием памяти, вы также можете написать модуль C / C ++.Используя Swig , оборачивание кода в Python может быть легко выполнено с небольшими потерями производительности по сравнению с чистым модулем Python на C.

...