Советы 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 может решить все ваши проблемы, даже немного ускорить процесс.
Если вы хотите остаться с хранилищем данных в памяти, вы можете попробовать что-то вроде memcached.
таким образом, вы можете использовать один ключ/хранилище значений в памяти из всех процессов Python.
существует несколько клиентских библиотек python memcached.
Redis было бы отличным вариантом здесь, Если у вас есть возможность использовать его на общем хосте - аналогично memcached, но оптимизировано для структур данных. Redis также поддерживает привязки python.
Я использую его изо дня в день для хруста чисел, но и в производственных системах в качестве хранилища данных и не могу рекомендовать его достаточно высоко.
кроме того, у вас есть возможность прокси-сервера вашего приложения за nginx вместо использования Apache? Вы можете найти (если разрешено) это расположение прокси / webapp менее голоден на ресурсах.
удачи.
Если вы хотите сделать обширную оптимизацию и иметь полный контроль над использованием памяти, вы также можете написать модуль C/C++. Используя глоток обертывание кода в Python можно сделать легко, с некоторыми небольшими накладными расходами по сравнению с чистым модулем C Python.