Загрузка большого словаря с помощью Python pickle

у меня есть полный перевернутый индекс в виде вложенного словаря python. Его структура :

{word : { doc_name : [location_list] } }

например, пусть словарь будет называться индексом, тогда для слова "спам" запись будет выглядеть так :

{ spam : { doc1.txt : [102,300,399], doc5.txt : [200,587] } }

я использовал эту структуру, поскольку Python dict довольно оптимизирован, и это упрощает Программирование.

для любого слова "спам" документы, содержащие его, могут быть предоставлены:

index['spam'].keys()

и список разноски для документа doc1 by:

index['spam']['doc1']

в настоящее время я использую cPickle для хранения и загрузки этого словаря. Но маринованный файл составляет около 380 МБ и занимает много времени для загрузки-112 секунд(прибл. Я приурочил его с помощью времени.time ()) и использование памяти идет на 1.2 GB (Системный монитор Gnome). Как только он загружается, все в порядке. У меня есть 4GB RAM.

len(index.keys()) дает 229758

код

import cPickle as pickle

f = open('full_index','rb')
print 'Loading index... please wait...'
index = pickle.load(f)  # This takes ages
print 'Index loaded. You may now proceed to search'

как я могу сделать это быстрее? мне нужно только загрузить его один раз, при запуске приложения. После этого важно время доступа для ответа на запросы.

должен ли я переключиться на базу данных, такую как SQLite, и создать индекс на ее ключах? Если да, то как сохранить значения для эквивалентной схемы, что упрощает извлечение. Есть что-нибудь еще, что я должен проверить ?

дополнительное соглашение

используя ответ Тима pickle.dump(index, file, -1) маринованный файл значительно меньше-около 237 МБ (потребовалось 300 секунд, чтобы сбросить)... и берет половина времени для загрузки сейчас (61 сек ... в отличие от 112 раньше .... времени.time ())

но должен ли я мигрировать в базу данных для масштабируемости ?

что касается сейчас, я отмечаю ответ Тима как принятый.

PS: Я не хочу использовать Lucene или Xapian ... Этот вопрос относится хранение перевернутого индекса . Мне пришлось задать новый вопрос, потому что я не смог удалить предыдущий.

5 ответов


попробуйте аргумент протокола при использовании cPickle.dump/cPickle.dumps. От cPickle.Pickler.__doc__:

Pickler (файл, протокол=0) -- создайте pickler.

для записи потока данных рассола требуется файлоподобный объект. Необязательный аргумент proto указывает сборщику использовать данный протокола; поддерживаемые протоколы: 0, 1, 2. Неисполнение протокол 0, чтобы быть обратно совместимы. (Протокол 0 является единственным протоколом, который может быть записан в файл, открытый в текстовом режим и читать обратно. При использовании протокола выше чем 0, убедитесь, что файл открыт в двоичном режиме, как при мариновать и unpickling.)

Протокол 1 более эффективен чем протокол 0; протокол 2 более эффективен, чем протокол 1.

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

параметр file должен иметь метод write (), который принимает один строковый аргумент. Таким образом, это может быть открытый файловый объект, StringIO объект или любой другой пользовательский объект, соответствующий этому интерфейсу.

преобразование JSON или YAML, вероятно, займет больше времени, чем маринование большую часть времени - рассол хранит собственные типы Python.


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

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

>>> from klepto.archives import dir_archive
>>> d = {'a':1, 'b':2, 'c':map, 'd':None}
>>> # map a dict to a filesystem directory
>>> demo = dir_archive('demo', d, serialized=True) 
>>> demo['a']
1
>>> demo['c']
<built-in function map>
>>> demo          
dir_archive('demo', {'a': 1, 'c': <built-in function map>, 'b': 2, 'd': None}, cached=True)
>>> # is set to cache to memory, so use 'dump' to dump to the filesystem 
>>> demo.dump()
>>> del demo
>>> 
>>> demo = dir_archive('demo', {}, serialized=True)
>>> demo
dir_archive('demo', {}, cached=True)
>>> # demo is empty, load from disk
>>> demo.load()
>>> demo
dir_archive('demo', {'a': 1, 'c': <built-in function map>, 'b': 2, 'd': None}, cached=True)
>>> demo['c']
<built-in function map>
>>> 

klepto есть и другие флаги, такие как compression и memmode это можно использовать для настройки способа хранения данных (например, уровень сжатия, режим карты памяти и т. д.). Одинаково легко (тот же самый точный интерфейс) использовать базу данных (MySQL и т. д.) В качестве бэкэнда вместо вашей файловой системы. Вы также можете отключить кэширование памяти, поэтому каждое чтение/запись идет непосредственно в архив, просто установив cached=False.

klepto обеспечивает доступ к настройке кодировки, построив пользовательский keymap.

>>> from klepto.keymaps import *
>>> 
>>> s = stringmap(encoding='hex_codec')
>>> x = [1,2,'3',min]
>>> s(x)
'285b312c20322c202733272c203c6275696c742d696e2066756e6374696f6e206d696e3e5d2c29'
>>> p = picklemap(serializer='dill')
>>> p(x)
'\x80\x02]q\x00(K\x01K\x02U\x013q\x01c__builtin__\nmin\nq\x02e\x85q\x03.'
>>> sp = s+p
>>> sp(x)
'\x80\x02UT28285b312c20322c202733272c203c6275696c742d696e2066756e6374696f6e206d696e3e5d2c292c29q\x00.' 

klepto также предоставляет множество алгоритмов кэширования (например,mru, lru, lfu и т. д.), Чтобы помочь вам управлять кэш в памяти, и будет использовать алгоритм, делать дамп и загрузить в архив, серверная часть для вас.

вы можете использовать флаг cached=False чтобы полностью отключить кэширование памяти и непосредственно читать и записывать на диск или базу данных. Если ваши записи достаточно велики, вы может выбрать запись на диск, где вы помещаете каждую запись в свой собственный файл. Вот пример, что делает оба.

>>> from klepto.archives import dir_archive
>>> # does not hold entries in memory, each entry will be stored on disk
>>> demo = dir_archive('demo', {}, serialized=True, cached=False)
>>> demo['a'] = 10
>>> demo['b'] = 20
>>> demo['c'] = min
>>> demo['d'] = [1,2,3]

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

Get klepto здесь: https://github.com/uqfoundation


общий шаблон в Python 2.x должен иметь одну версию модуля, реализованную в чистом Python, с дополнительной ускоренной версией, реализованной как расширение C; например, pickle и cPickle. Это возлагает бремя импорта ускоренной версии и возврата к чистой версии Python на каждого пользователя этих модулей. В Python 3.0, ускоренные версии считаются деталями реализации чистых версий Python. пользователи всегда должны импортируйте стандартную версию, которая пытается импортировать ускоренную версию и возвращается к чистой версии Python. пара рассола / cPickle получила это лечение.

  • Protocol version 0 является оригинальным" читаемым человеком " протоколом и обратно совместим с более ранними версиями Python.
  • Protocol version 1-это старый двоичный формат, который также совместим с более ранними версиями Python.
  • протокол версия 2 была представлена в Python 2.3. Он обеспечивает гораздо более эффективное маринование классов нового стиля. См. ОПТОСОЗ 307 для получения информации об улучшениях, внесенных протоколом 2.
  • протокол версии 3 был добавлен в Python 3.0. Он имеет явную поддержку объектов bytes и не может быть распакован Python 2.х. это протокол по умолчанию и рекомендуемый протокол, когда совместимость с другими версиями Python 3 требуемый.
  • версия протокола 4 была добавлена в Python 3.4. Он добавляет поддержку очень больших объектов, маринование больше видов объектов и некоторые оптимизации формата данных. См.PEP 3154 для получения информации об улучшениях, внесенных протоколом 4.

если ваш словарь огромен и должен быть совместим только с Python 3.4 или выше, используйте:

pickle.dump(obj, file, protocol=4)
pickle.load(file, encoding="bytes")

или:

Pickler(file, 4).dump(obj)
Unpickler(file).load()

что сказал, в 2010 году the json модуль был в 25 раз быстрее при кодировании и в 15 раз быстрее при декодировании простых типов, чем pickle. мой 2014 benchmark говорит marshal>pickle>json, а marshal's в сочетании с конкретными версиями Python.


вы пробовали использовать альтернативный формат хранения, такой как в YAML или в формате JSON? Python поддерживает JSON изначально из Python 2.6, используя json модуль, я думаю, и есть сторонние модули для YAML.

вы также можете попробовать shelve модуль.


зависит от того, как долго "долго" вы должны думать о компромиссах, которые вы должны сделать: либо иметь все данные, готовые в памяти после (долгого) запуска, либо загружать только частичные данные (тогда вам нужно разделить дату на несколько файлов или использовать SQLite или что-то вроде этого). Я сомневаюсь, что загрузка всех данных заранее из, например, sqlite в словарь принесет какие-либо улучшения.