Советы Python по оптимизации памяти

Мне нужно оптимизировать использование ОЗУ моего приложения.
Пожалуйста, избавьте меня от лекций, говорящих мне, что я не должен заботиться о памяти при кодировании Python. У меня проблема с памятью, потому что я использую очень большие словари по умолчанию (да, я также хочу быть быстрым). Мое текущее потребление памяти составляет 350MB и растет. Я уже не могу использовать общий хостинг, и если мой Apache открывает больше процессов, память удваивается и утрояется... и это дорого.
Я сделал широкое профайлинг и я точно знаю, где мои проблемы.
У меня есть несколько больших (>100k записей) словарей с ключами Unicode. Словарь начинается со 140 байт и быстро растет, но большая проблема-это ключи. Python оптимизирует строки в памяти (или так я читал), чтобы поисковые запросы могли быть сравнениями ID ("интернирование" их). Не уверен, что это также верно для строк unicode (я не смог "интернировать" их).
Объекты, хранящиеся в словаре, являются списками кортежей (an_object, an инт, инт).

my_big_dict[some_unicode_string].присоеденить((my_object, an_int, another_int))

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

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

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

7 ответов


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

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

как только у вас есть эффективная для памяти реализация словаря int-int, разделите свой строка → (object, int, int) словарь в три словаря и использовать хэши вместо полных строк. Вы получите один int → объект и два int → int словари. Эмулировать int → объект словарь следующим образом: сохранить список объектов и хранить индексы объектов как значения 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-й массив. Массивы инициализируются по требованию и упорядочиваются по ключам. Двоичный поиск выполняется при каждом чтении и записи. Проверка столкновения выбор. Если этот параметр включен, попытка перезаписи уже существующего ключа приведет к удалению ключа и связанного с ним значения из словаря, добавлению ключа в набор сталкивающихся ключей и (опять же, необязательно) возникновению исключения.


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


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

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


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


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

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

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


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

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

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

удачи.


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