python - PyTables que tratan con datos con un tamaño muchas veces mayor que el tamaño de la memoria
io hdf5 (3)
Estoy tratando de entender cómo manejan PyTables los datos cuyo tamaño es mayor que el tamaño de la memoria. Aquí está el comentario en el código de PyTables ( enlace a GitHub ):
# Nodes referenced by a variable are kept in `_aliveNodes`.
# When they are no longer referenced, they move themselves
# to `_deadNodes`, where they are kept until they are referenced again
# or they are preempted from it by other unreferenced nodes.
También se pueden encontrar comentarios útiles dentro del método _getNode .
Parece que PyTables tiene un sistema de almacenamiento en IO muy inteligente que, según tengo entendido, almacena los datos referenciados por el usuario en RAM rápida como "aliveNodes", mantiene los datos referenciados antes y actualmente sin referencia como "deadNodes" para "reactivar" rápidamente cuando sea necesario, y lee los datos del disco si la clave solicitada no está presente en las categorías de muertos o vivos.
Necesito algo de experiencia sobre cómo exactamente PyTables manejan situaciones cuando se trabaja con datos más grandes que la memoria disponible. Mis preguntas específicas:
- ¿Cómo funciona el sistema deadNode / aliveNode (imagen común)?
- ¿Cuál es la diferencia clave entre aliveNodes / deadNodes mientras que ambos representan los datos almacenados en la memoria RAM si estoy bien?
- ¿Se puede ajustar manualmente el límite de RAM para el almacenamiento en búfer? Debajo del comentario, hay un código que lee un valor de
params[''NODE_CACHE_SLOTS'']
. ¿Puede ser especificado de alguna manera por el usuario? Por ejemplo, si quiero dejar algo de RAM para otras aplicaciones que también necesitan memoria. - ¿En qué situaciones las PyTables pueden bloquearse o ralentizarse significativamente cuando se trabaja con gran cantidad de datos? En mi caso, puede exceder 100 veces la memoria, ¿cuáles son las dificultades comunes en tales situaciones?
- ¿Qué uso de PyTables en cuanto a tamaño, estructura de datos y también manipulaciones con datos considerados como "correctos" para lograr el mejor rendimiento?
- Docs sugiere utilizar
.flush()
después de cada.append()
básico.append()
. ¿Cuánto tiempo este ciclo realmente puede ser? Estoy realizando un pequeño punto de referencia, comparando SQLite y PyTables en cómo pueden manejar la creación de una gran tabla con pares clave-valor de grandes archivos CSV. Y cuando uso.flush()
, con menos frecuencia en el ciclo principal, PyTables gana una aceleración enorme. Entonces, ¿es correcto, para.append()
trozos relativamente grandes de datos, y luego usar.flush()
?
No soy un experto en PyTable 1 pero lo más probable es que funcione como memoria de intercambio .
Los aliveNodes
vivos viven en la RAM, mientras que los deadNodes
probablemente se almacenan en el disco en archivos hdf5 (el formato de archivo binario utilizado por PyTables). Cada vez que necesite acceder a un dato, debe estar en la memoria RAM. Así que PyTable verifica si ya está allí ( aliveNodes
) y se lo devuelve si lo está. De lo contrario, necesita revivir el deadNode
donde deadNode
los datos. Como la RAM es limitada, probablemente mate (escriba en el disco) un aliveNode
no aliveNode
para hacer espacio de antemano.
La razón de este proceso es, por supuesto, el tamaño limitado de la memoria RAM. La consecuencia es que las interpretaciones se ven afectadas cada vez que necesita intercambiar un nodo ( matar un nodo y revivir a otro).
Para optimizar el rendimiento, debe intentar minimizar el intercambio. Por ejemplo, si sus datos se pueden procesar en paralelo, puede cargar cada nodo solo una vez. Otro ejemplo: imagine que necesita pasar por encima de cada elemento de una matriz enorme que se divide en una cuadrícula de nodos. Entonces será mejor que evite acceder a sus elementos por fila o por columna, sino más bien nodo por nodo.
Por supuesto, PyTable maneja esto bajo el capó, por lo que no es necesario tener control sobre lo que hay en cada nodo (pero lo animo a que NODE_CACHE_SLOTS
esta variable NODE_CACHE_SLOTS
, al menos para entender cómo funciona). Pero, en general, es más rápido acceder a datos que son contiguos en lugar de dispersos por todo el lugar. Como siempre, si el rendimiento en el tiempo es un problema importante para su (s) aplicación (es), perfile su código.
1 Traducción: Apenas sé nada sobre PyTables
Estructura de memoria
Nunca usé pytables pero mirando el código fuente:
class _Deadnodes(lrucacheExtension.NodeCache):
pass
Así que parece que los _deadnodes se implementan usando un caché LRU. LRU == "Usado menos recientemente" lo que significa que tirará primero el nodo menos usado. la fuente está aquí .
class _AliveNodes(dict):
...
Que utilizan como un diccionario personalizado de nodos que se ejecutan y representan en realidad en el programa.
ejemplo muy simplificado (los nodos son letras, los números en el caché indican qué tan obsoleta es una entrada):
memory of 4, takes 1 time step
cache with size 2, takes 5 times steps
disk with much much more, takes 50 time steps
get node A //memory,cache miss load from disk t=50
get node B // "" t=100
get node C // "" t=150
get node D // "" t=200
get node E // "" t=250
get node A //cache hit load from cache t=255
get node F //memory, cache miss load from disk t=305
get node G //memory, cache miss load from disk t=355
get node E // in memory t=356 (everything stays the same)
t=200 t=250 t=255
Memory CACHE Memory CACHE Memory CACHE
A E A0 E B0
B B A
C C C
D D D
t=305 t=355
Memory CACHE Memory CACHE
E B1 E G0
A C0 A C1
F F
D G
Como sabes en la vida real, estas estructuras son enormes y el tiempo que lleva acceder a ellas es en ciclos de bus, por lo que 1 / (reloj de tu pc).
Comparativamente, el tiempo que lleva acceder a los elementos es el mismo. Es bastante despreciable para la memoria, un poco más para la memoria caché y mucho más para el disco. Leer desde el disco es la parte más larga de todo el proceso. el disco y el brazo necesitan moverse, etc. Es un proceso físico en lugar de un proceso electrónico, ya que no está sucediendo a la velocidad de la luz.
Aquí en Pytables hacen algo similar. Han escrito su propio algoritmo de caché en Cython que es un intermediario entre los nodos vivos (memoria) y los datos completos (disco). Si hay una relación de aciertos demasiado baja, parece que la memoria caché se apagará, y después de un cierto número de ciclos se volverá a encender.
En parameters.py, las DISABLE_EVERY_CYCLE
, ENABLE EVERY_CYCLE
y LOWEST_HIT_RATIO
se usan para definir el número de ciclos en LOWEST_HIT_RATIO para deshabilitar después y el número de ciclos para esperar a volver a habilitar. Se desaconseja cambiar estos valores.
Lo principal que debe tomar de esto es que si necesita procesar en un gran conjunto de datos, asegúrese de que estén en los mismos nodos. Si puede salirse con la suya, lea en un pedazo, haga el procesamiento en ese plato, obtenga los resultados, luego cargue otro pedazo. Si cargas el trozo A, obtienes otro trozo B y vuelves a cargar el trozo A, esto provocará el mayor retraso. Solo opere en un fragmento de datos a la vez y mantenga el acceso y las escrituras al mínimo. Una vez que un valor está en _alivenodes
, es rápido modificarlo, _deadnodes
es un poco más lento, y ninguno es mucho más lento.
NODE_CACHE_SLOTS
params[''NODE_CACHE_SLOTS'']
define el tamaño del conjunto de nodos muertos. Al reenviarlo a parameters.py , el valor predeterminado es 64. Indica que puede probar diferentes valores e informar. Puede cambiar el valor en el archivo o hacer:
import parameters
parameters.NODE_CACHE_SLOTS = # something else
Esto solo limita la cantidad de nodos guardados en la memoria caché. Más allá de que está limitado por el tamaño del montón de Python, para establecer que vea esto .
añadir / enjuagar
Para append
, flush
asegura que las filas se envíen a la tabla. Cuantos más datos esté moviendo con esto, más tiempo tardará la información en pasar del búfer interno a la estructura de datos. Está llamando a versiones modificadas de la función H5TBwrite_records con otro código de manejo. Supongo que la duración de la llamada a eso determina cuánto durará el ciclo de salida.
Tenga en cuenta que todo esto proviene del código fuente, y no está considerando ninguna magia adicional que estén tratando de hacer. Nunca he usado pytables. En teoría, no debería colapsar, pero no vivimos en un mundo teórico.
Editar:
En realidad, al encontrar una necesidad de pytables, me he encontrado con esta pregunta en sus preguntas frecuentes que podría responder a algunas de sus preocupaciones.
Gracias por exponerme a Pytables, si hubiera encontrado archivos .h5
antes de investigar esta pregunta, no habría sabido qué hacer.
Tampoco soy un experto en PyTable, y Simon parece haber cubierto el concepto de memoria de intercambio de forma agradable, PERO si desea un ejemplo concreto de un algoritmo diseñado para manejar datos demasiado grandes para caber en la memoria, le recomiendo mirar tipo externo
La idea básica es esta: no puede incluir todos sus datos en la memoria, pero debe ordenarlos. Sin embargo, puede ajustar algunos de los datos en la memoria, en bloques de tamaño k. Dicen que hay j tales bloques.
- Divida los datos en bloques de tamaño k.
- Para cada bloque, tráigalo a la memoria y ordénelo (p. Ej., Usando un quicksort o lo que sea) y luego vuelva a escribir su versión ordenada en el disco.
Ahora, tenemos j bloques de datos ordenados que queremos fusionar en una larga serie de datos ordenados. ¡Ese problema suena como Mergesort! Asi que,
- Traiga a la memoria el valor más bajo de cada uno de los bloques ordenados j
- Encuentra el más pequeño de esos j valores. ¡Esa es la información más pequeña! Por lo tanto, escríbalo en un disco en algún lugar como el inicio de nuestro conjunto de datos ordenados.
- Reemplace el valor recién escrito con el siguiente valor más pequeño de su bloque en la memoria (este es el bit de "intercambio" de la memoria de intercambio).
Ahora, los datos en memoria son los j más pequeños, excepto el que ya escribimos en el conjunto de datos ordenados finales en el disco. Entonces, si repetimos ese proceso hasta que todos los datos se escriban en el conjunto final, siempre se ordenarán.
Entonces, eso es solo un ejemplo de un algoritmo que usa el intercambio de memoria para manejar datos demasiado grandes para caber en la memoria. Los métodos de clasificación de PyTable son probablemente en esta línea.
Bono: Aquí hay algunos enlaces a más explicaciones de tipo externo.