python - ejemplos - django
La forma más eficiente de almacenar la lista de enteros (3)
Recientemente he estado haciendo un proyecto en el que uno de los objetivos es utilizar la menor cantidad de memoria posible para almacenar una serie de archivos usando Python 3. Casi todos los archivos ocupan muy poco espacio, aparte de una lista de enteros que es aproximadamente 333,000
enteros de largo y tiene enteros de hasta aproximadamente 8000
en tamaño.
Actualmente uso pickle
para almacenar la lista, que ocupa alrededor de 7 7mb
, pero creo que debe haber una forma más eficiente de hacerlo.
Intenté almacenarlo como un archivo de texto y csv
, pero ambos se usaron en exceso de 10mb
de espacio.
Aquí hay una pequeña demostración, que usa el módulo Pandas:
import numpy as np
import pandas as pd
import feather
# let''s generate an array of 1M int64 elements...
df = pd.DataFrame({''num_col'':np.random.randint(0, 10**9, 10**6)}, dtype=np.int64)
df.info()
%timeit -n 1 -r 1 df.to_pickle(''d:/temp/a.pickle'')
%timeit -n 1 -r 1 df.to_hdf(''d:/temp/a.h5'', ''df_key'', complib=''blosc'', complevel=5)
%timeit -n 1 -r 1 df.to_hdf(''d:/temp/a_blosc.h5'', ''df_key'', complib=''blosc'', complevel=5)
%timeit -n 1 -r 1 df.to_hdf(''d:/temp/a_zlib.h5'', ''df_key'', complib=''zlib'', complevel=5)
%timeit -n 1 -r 1 df.to_hdf(''d:/temp/a_bzip2.h5'', ''df_key'', complib=''bzip2'', complevel=5)
%timeit -n 1 -r 1 df.to_hdf(''d:/temp/a_lzo.h5'', ''df_key'', complib=''lzo'', complevel=5)
%timeit -n 1 -r 1 feather.write_dataframe(df, ''d:/temp/a.feather'')
Información de DataFrame:
In [56]: df.info()
<class ''pandas.core.frame.DataFrame''>
RangeIndex: 1000000 entries, 0 to 999999
Data columns (total 1 columns):
num_col 1000000 non-null int64
dtypes: int64(1)
memory usage: 7.6 MB
Resultados (velocidad):
In [49]: %timeit -n 1 -r 1 df.to_pickle(''d:/temp/a.pickle'')
1 loop, best of 1: 16.2 ms per loop
In [50]: %timeit -n 1 -r 1 df.to_hdf(''d:/temp/a.h5'', ''df_key'', complib=''blosc'', complevel=5)
1 loop, best of 1: 39.7 ms per loop
In [51]: %timeit -n 1 -r 1 df.to_hdf(''d:/temp/a_blosc.h5'', ''df_key'', complib=''blosc'', complevel=5)
1 loop, best of 1: 40.6 ms per loop
In [52]: %timeit -n 1 -r 1 df.to_hdf(''d:/temp/a_zlib.h5'', ''df_key'', complib=''zlib'', complevel=5)
1 loop, best of 1: 213 ms per loop
In [53]: %timeit -n 1 -r 1 df.to_hdf(''d:/temp/a_bzip2.h5'', ''df_key'', complib=''bzip2'', complevel=5)
1 loop, best of 1: 1.09 s per loop
In [54]: %timeit -n 1 -r 1 df.to_hdf(''d:/temp/a_lzo.h5'', ''df_key'', complib=''lzo'', complevel=5)
1 loop, best of 1: 32.1 ms per loop
In [55]: %timeit -n 1 -r 1 feather.write_dataframe(df, ''d:/temp/a.feather'')
1 loop, best of 1: 3.49 ms per loop
Resultados (tamaño):
{ temp } » ls -lh a* /d/temp
-rw-r--r-- 1 Max None 7.7M Sep 20 23:15 a.feather
-rw-r--r-- 1 Max None 4.1M Sep 20 23:15 a.h5
-rw-r--r-- 1 Max None 7.7M Sep 20 23:15 a.pickle
-rw-r--r-- 1 Max None 4.1M Sep 20 23:15 a_blosc.h5
-rw-r--r-- 1 Max None 4.0M Sep 20 23:15 a_bzip2.h5
-rw-r--r-- 1 Max None 4.1M Sep 20 23:15 a_lzo.h5
-rw-r--r-- 1 Max None 3.9M Sep 20 23:15 a_zlib.h5
Conclusión: preste atención a HDF5 ( blosc
+ blosc
o lzo
) si necesita velocidad y tamaño razonable o en formato Feather si solo le importa la velocidad: ¡es 4 veces más rápido que Pickle!
Una solución stdlib
que puede usar son las matrices de una array
, de los documentos:
Este módulo define un tipo de objeto que puede representar de forma compacta una matriz de valores básicos: caracteres, números enteros, números en coma flotante. Las matrices son tipos de secuencia y se comportan de forma muy similar a las listas, excepto que el tipo de objetos almacenados en ellas está restringido.
Esto generalmente arroja un poco de memoria de listas grandes, por ejemplo, con una lista de 10 millones de elementos, la matriz recorta 11mb
:
import pickle
from array import array
l = [i for i in range(10000000)]
a = array(''i'', l)
# tofile can also be used.
with open(''arrfile'', ''wb'') as f:
pickle.dump(a, f)
with open(''lstfile'', ''wb'') as f:
pickle.dump(l, f)
Tamaños:
!du -sh ./*
39M arrfile
48M lstfile
Me gusta la sugerencia de Jim de usar el módulo de array
. Si sus valores numéricos son lo suficientemente pequeños como para caber en el tipo int
nativo de la máquina, esta es una buena solución. (Preferiría serializar la matriz con el método array.tofile
lugar de usar pickle
, sin embargo). Si una int
es de 32 bits, entonces esto usa 4 bytes por número.
Sin embargo, me gustaría cuestionar cómo hiciste tu archivo de texto. Si creo un archivo con 333 000 enteros en el rango [0, 8 000] con un número por línea,
import random
with open(''numbers.txt'', ''w'') as ostr:
for i in range(333000):
r = random.randint(0, 8000)
print(r, file=ostr)
sale a un tamaño de solo 1.6 MiB, que no es tan malo en comparación con el 1.3 MiB que usaría la representación binaria. Y si sucede que tiene un valor fuera del rango del tipo int
nativo un día, el archivo de texto lo manejará felizmente sin desbordamiento.
Además, si comprimo el archivo usando gzip, el tamaño del archivo se reduce a 686 KiB. ¡Eso es mejor que copiar los datos binarios! Al usar bzip2, el tamaño del archivo es de solo 562 KiB. La biblioteca estándar de Python tiene soporte tanto para gzip
como para bz2
por lo que es posible que desee probar el formato de texto sin formato más la compresión.