lista - import io python
Python readlines() uso y práctica eficiente para leer (2)
La versión corta es: la manera eficiente de usar readlines()
es no usarla. Nunca.
Leí algunas notas del documento en
readlines()
, donde las personas han afirmado quereadlines()
lee todo el contenido del archivo en la memoria y, por lo tanto, generalmente consume más memoria en comparación con readline () o read ().
La documentación de readlines()
garantiza explícitamente que lee todo el archivo en la memoria, lo analiza en líneas y crea una list
llena de strings de esas líneas.
Pero la documentación de read()
también garantiza que lea el archivo completo en la memoria y construya un texto, por lo que no ayuda.
Además de usar más memoria, esto también significa que no puedes hacer ningún trabajo hasta que se lea todo. Si alterna la lectura y el procesamiento incluso de la manera más ingenua, se beneficiará al menos de una canalización (gracias a la memoria caché de disco del sistema operativo, DMA, canalización de CPU, etc.), por lo que trabajará en un lote mientras que el siguiente lote está siendo leído Pero si fuerza a la computadora a leer todo el archivo, luego analiza el archivo completo, luego ejecuta su código, solo obtiene una región de trabajo superpuesto para todo el archivo, en lugar de una región de trabajo superpuesto por lectura.
Puede solucionar esto de tres maneras:
- Escriba un ciclo alrededor de
readlines(sizehint)
,read(size)
oreadline()
. - Simplemente use el archivo como un iterador perezoso sin llamar a ninguno de estos.
-
mmap
el archivo, que le permite tratarlo como una cadena gigante sin leerlo primero.
Por ejemplo, esto tiene que leer todo de foo
a la vez:
with open(''foo'') as f:
lines = f.readlines()
for line in lines:
pass
Pero esto solo dice 8K a la vez:
with open(''foo'') as f:
while True:
lines = f.readlines(8192)
if not lines:
break
for line in lines:
pass
Y esto solo lee una línea a la vez, aunque Python puede (y lo hará) elegir un buen tamaño de búfer para hacer las cosas más rápido.
with open(''foo'') as f:
while True:
line = f.readline()
if not line:
break
pass
Y esto hará exactamente lo mismo que el anterior:
with open(''foo'') as f:
for line in f:
pass
Mientras tanto:
pero, ¿debería el recolector de basura borrar automáticamente el contenido cargado de la memoria al final de mi ciclo, por lo tanto, en cualquier momento, mi memoria debería tener solo el contenido de mi archivo procesado actualmente correcto?
Python no hace ninguna de esas garantías sobre la recolección de basura.
La implementación de CPython usa el refcounting para GC, lo que significa que en el código, tan pronto como file_content
obtiene rebote o desaparece, la lista gigante de cadenas y todas las cadenas dentro de ella se liberará al librero, lo que significa que la misma memoria se puede volver a utilizar para su próximo pase.
Sin embargo, todas esas asignaciones, copias y desasignaciones no son gratuitas; es mucho más rápido no hacerlas que hacerlas.
Además de eso, tener tus cadenas dispersas en una gran franja de memoria en lugar de reutilizar el mismo pequeño trozo de memoria una y otra vez perjudica el comportamiento de tu caché.
Además, aunque el uso de la memoria puede ser constante (o, más bien, lineal en el tamaño de su archivo más grande, en lugar de en la suma de los tamaños de sus archivos), esa avalancha de malloc
para expandirla la primera vez será una de las cosas más lentas que haces (lo que también hace que sea mucho más difícil hacer comparaciones de rendimiento).
Poniéndolo todo junto, así es como escribiría su programa:
for filename in os.listdir(input_dir):
with open(filename, ''rb'') as f:
if filename.endswith(".gz"):
f = gzip.open(fileobj=f)
words = (line.split(delimiter) for line in f)
... my logic ...
O tal vez:
for filename in os.listdir(input_dir):
if filename.endswith(".gz"):
f = gzip.open(filename, ''rb'')
else:
f = open(filename, ''rb'')
with contextlib.closing(f):
words = (line.split(delimiter) for line in f)
... my logic ...
Tengo un problema para analizar miles de archivos de texto (alrededor de 3000 líneas en cada archivo de ~ 400 KB de tamaño) en una carpeta. Los leí usando readlines,
for filename in os.listdir (input_dir) :
if filename.endswith(".gz"):
f = gzip.open(file, ''rb'')
else:
f = open(file, ''rb'')
file_content = f.readlines()
f.close()
len_file = len(file_content)
while i < len_file:
line = file_content[i].split(delimiter)
... my logic ...
i += 1
Esto funciona completamente bien para la muestra de mis entradas (50,100 archivos). Cuando ejecuté en toda la entrada más de 5K archivos, el tiempo empleado no se acercaba al incremento lineal. Planeé hacer un análisis de rendimiento e hice un análisis Cprofile. El tiempo que tardan los archivos en aumentar exponencialmente al llegar a peores tasas cuando las entradas llegan a los archivos 7K.
Aquí está el tiempo acumulado para líneas de lectura, primero -> 354 archivos (muestra de entrada) y segundo -> archivos 7473 (entrada completa)
ncalls tottime percall cumtime percall filename:lineno(function)
354 0.192 0.001 **0.192** 0.001 {method ''readlines'' of ''file'' objects}
7473 1329.380 0.178 **1329.380** 0.178 {method ''readlines'' of ''file'' objects}
Debido a esto, el tiempo empleado por mi código no se escala linealmente a medida que aumenta la entrada. Leí algunas notas del documento en readlines()
, donde las personas han afirmado que readlines()
lee todo el contenido del archivo en la memoria y, por lo tanto, generalmente consume más memoria en comparación con readline()
o read()
.
Estoy de acuerdo con este punto, pero ¿debería el recolector de basura borrar automáticamente el contenido cargado de la memoria al final de mi ciclo, por lo tanto, en cualquier momento, mi memoria debería tener solo el contenido de mi archivo procesado actualmente correcto? Pero, hay algo de captura aquí. ¿Alguien puede dar algunas ideas sobre este tema?
¿Es este un comportamiento inherente de readlines()
o mi interpretación incorrecta del recolector de basura de python? Contento de saber.
Además, sugiera algunas formas alternativas de hacer lo mismo en forma eficiente de memoria y tiempo. TIA.
Lea línea por línea, no el archivo completo:
for line in open(file_name, ''rb''):
# process line here
Mejor uso with
para cerrar automáticamente el archivo:
with open(file_name, ''rb'') as f:
for line in f:
# process line here
Lo anterior leerá el objeto de archivo usando un iterador, una línea a la vez.