Cargando un diccionario grande usando pepinillo picón

Tengo un índice invertido completo en forma de diccionario de Python nested. Su estructura es:

{word : { doc_name : [location_list] } } 

Por ejemplo, permita que el diccionario se llame índice, luego para una palabra “spam”, la entrada se vería así:

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

Utilicé esta estructura ya que los dictados de Python están bastante optimizados y facilita la progtwigción.

Para cualquier palabra ‘spam’, los documentos que la contienen pueden ser dados por:

 index['spam'].keys() 

y lista de publicación para un documento doc1 por:

 index['spam']['doc1'] 

Actualmente estoy usando cPickle para almacenar y cargar este diccionario. Pero el archivo encurtido tiene alrededor de 380 MB y tarda mucho tiempo en cargarse – 112 segundos (aproximadamente. Lo cronometré usando time.time () ) y el uso de la memoria es de 1.2 GB (monitor del sistema Gnome). Una vez que se carga, está bien. Tengo 4GB de RAM.

len(index.keys()) da 229758

Código

 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' 

¿Cómo puedo hacer que se cargue más rápido? Solo necesito cargarlo una vez, cuando se inicia la aplicación. Después de eso, el tiempo de acceso es importante para responder a las consultas.

¿Debo cambiar a una base de datos como SQLite y crear un índice en sus claves? En caso afirmativo, ¿cómo almaceno los valores para tener un esquema equivalente, lo que facilita la recuperación? ¿Hay algo más que debería mirar?

Apéndice

Usando la respuesta de Tim pickle.dump(index, file, -1) el archivo encurtido es considerablemente más pequeño: alrededor de 237 MB (demoró 300 segundos en volcarse) … y demora la mitad del tiempo en cargarse ahora (61 segundos … como opuesto a 112 s antes …. time.time () )

¿Pero debo migrar a una base de datos para la escalabilidad?

En cuanto a ahora estoy marcando la respuesta de Tim como aceptada.

PD: no quiero usar a Lucene ni a Xapian … Esta pregunta se refiere al almacenamiento de un índice invertido . Tuve que hacer una nueva pregunta porque no pude eliminar la anterior.

Pruebe el argumento del protocolo cuando use cPickle.dump / cPickle.dumps . Desde cPickle.Pickler.__doc__ :

Pickler (archivo, protocolo = 0) – Crea un pickler.

Esto toma un objeto similar a un archivo para escribir un flujo de datos de salmuera. El argumento proto opcional le dice al pickler que use el protocolo dado; los protocolos admitidos son 0, 1, 2. El protocolo predeterminado es 0, para ser compatible con versiones anteriores. (El protocolo 0 es el único protocolo que se puede escribir en un archivo abierto en modo de texto y leer con éxito. Cuando se usa un protocolo superior a 0, asegúrese de que el archivo se abra en modo binario, tanto al seleccionar como al desmenuzar).

El protocolo 1 es más eficiente que el protocolo 0; El protocolo 2 es más eficiente que el protocolo 1.

Al especificar una versión de protocolo negativa, se selecciona la versión de protocolo más alta admitida. Cuanto más alto es el protocolo utilizado, más reciente es la versión de Python que se necesita para leer el pickle producido.

El parámetro de archivo debe tener un método write () que acepte un único argumento de cadena. Por lo tanto, puede ser un objeto de archivo abierto, un objeto StringIO o cualquier otro objeto personalizado que cumpla con esta interfaz.

La conversión de JSON o YAML probablemente llevará más tiempo que el decapado la mayor parte del tiempo: las tiendas de pickle tipo Python nativo.

¿Realmente lo necesitas para cargar todo de una vez? Si no lo necesita todo en la memoria, sino solo las partes seleccionadas que desea en un momento dado, es posible que desee asignar su diccionario a un conjunto de archivos en el disco en lugar de a un solo archivo … o asignar el dictado a un tabla de base de datos Por lo tanto, si está buscando algo que guarde grandes diccionarios de datos en el disco o en una base de datos, y pueda utilizar decapado y encoding (códecs y hashmaps), es posible que desee ver klepto .

klepto proporciona una abstracción del diccionario para escribir en una base de datos, incluido el tratamiento de su sistema de archivos como una base de datos (es decir, escribir todo el diccionario en un solo archivo o escribir cada entrada en su propio archivo). Para datos grandes, a menudo elijo representar el diccionario como un directorio en mi sistema de archivos, y cada entrada es un archivo. klepto también ofrece algoritmos de almacenamiento en caché, por lo que si está utilizando un sistema de archivos para el diccionario, puede evitar algunas penalizaciones de velocidad utilizando el almacenamiento en memoria caché.

 >>> 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']  >>> demo dir_archive('demo', {'a': 1, 'c': , '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': , 'b': 2, 'd': None}, cached=True) >>> demo['c']  >>> 

klepto también tiene otros indicadores como compression y modo de memmode que se pueden usar para personalizar cómo se almacenan los datos (por ejemplo, nivel de compresión, modo de mapa de memoria, etc.). Es igualmente fácil (la misma interfaz exacta) usar una base de datos (MySQL, etc.) como un servidor en lugar de su sistema de archivos. También puede desactivar el almacenamiento en memoria caché, por lo que cada lectura / escritura va directamente al archivo, simplemente configurando cached=False .

klepto proporciona acceso a la personalización de su encoding, mediante la creación de un klepto personalizado.

 >>> 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 también proporciona una gran cantidad de algoritmos de almacenamiento en caché (como mru , lru , lfu , etc.), para ayudarlo a administrar su caché en memoria, y usará el algoritmo para realizar el volcado y la carga en el lru lfu para usted.

Puede usar el indicador cached=False para desactivar completamente el caché de memoria, y leer y escribir directamente en y desde el disco o la base de datos. Si sus entradas son lo suficientemente grandes, puede optar por escribir en el disco, donde coloca cada entrada en su propio archivo. Aquí hay un ejemplo que hace ambas cosas.

 >>> 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] 

Sin embargo, aunque esto debería reducir en gran medida el tiempo de carga, puede ralentizar un poco la ejecución general … por lo general, es mejor especificar la cantidad máxima que se debe mantener en la memoria caché y elegir un buen algoritmo de almacenamiento en caché. Tienes que jugar con él para conseguir el equilibrio adecuado para tus necesidades.

Obtenga klepto aquí: https://github.com/uqfoundation

Un patrón común en Python 2.x es tener una versión de un módulo implementado en Python puro, con una versión acelerada opcional implementada como una extensión C; por ejemplo, pickle y cPickle . Esto coloca la carga de importar la versión acelerada y recurrir a la versión Python pura en cada usuario de estos módulos. En Python 3.0 , las versiones aceleradas se consideran detalles de implementación de las versiones puras de Python. Los usuarios siempre deben importar la versión estándar, que intenta importar la versión acelerada y vuelve a la versión Python pura. El par pickle / cPickle recibió este tratamiento.

  • La versión 0 del protocolo es el protocolo original “legible por humanos” y es compatible con versiones anteriores de Python.
  • La versión 1 del protocolo es un formato binario antiguo que también es compatible con versiones anteriores de Python.
  • La versión 2 del protocolo se introdujo en Python 2.3. Proporciona un decapado mucho más eficiente de las clases de nuevo estilo. Consulte la PEP 307 para obtener información sobre las mejoras traídas por el protocolo 2.
  • Protocolo versión 3 fue añadido en Python 3.0. Tiene soporte explícito para objetos de bytes y no puede ser eliminado por Python 2.x. Este es el protocolo predeterminado, y el protocolo recomendado cuando se requiere compatibilidad con otras versiones de Python 3.
  • Protocolo versión 4 fue añadido en Python 3.4. Agrega soporte para objetos muy grandes , escogiendo más tipos de objetos y algunas optimizaciones de formato de datos. Consulte la PEP 3154 para obtener información sobre las mejoras traídas por el protocolo 4.

Si su diccionario es enorme y solo debería ser compatible con Python 3.4 o superior, use:

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

o:

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

Dicho esto, en 2010, el módulo json fue 25 veces más rápido en la encoding y 15 veces más rápido en la desencoding de tipos simples que el pickle . Mi punto de referencia de 2014 dice marshal > pickle > json , pero marshal's acoplado a versiones específicas de Python .

¿Ha intentado usar un formato de almacenamiento alternativo como YAML o JSON ? Python admite JSON de forma nativa desde Python 2.6 usando el módulo json , creo, y hay módulos de terceros para YAML .

También puede probar el módulo de shelve .

Depende de cuánto tiempo sea “largo” debe pensar en las compensaciones que debe realizar: tener todos los datos listos en la memoria después del inicio (largo) o cargar solo datos parciales (entonces debe dividir la fecha en múltiples archivos o usar SQLite o algo como esto). Dudo que cargar todos los datos por adelantado desde, por ejemplo, sqlite en un diccionario traerá alguna mejora.