[LINUX-06-2] Colección de "basura" de Python

Prólogo

Para Python, todo es un objeto, y todas las asignaciones de variables siguen el mecanismo de referencia del objeto. Cuando el programa se está ejecutando, es necesario crear un espacio en la memoria para almacenar variables temporales generadas durante la operación; una vez que se completa el cálculo, los resultados se envían a la memoria permanente. Si la cantidad de datos es demasiado grande, la gestión deficiente del espacio de memoria es propensa a OOM (sin memoria), comúnmente conocida como memoria de ráfaga, y el sistema operativo puede cancelar el programa. Para el servidor, la administración de la memoria es más importante, de lo contrario es fácil causar una pérdida de memoria; la pérdida aquí no significa que su memoria tenga problemas de seguridad de la información y que sea utilizada por programas maliciosos, sino que el programa en sí no está bien diseñado. Hace que el programa no libere memoria que ya no se usa. -Una pérdida de memoria no significa que su memoria haya desaparecido físicamente, pero significa que después de que el código asigna una cierta sección de memoria, pierde el control de esta sección de memoria debido a un error de diseño, lo que resulta en un desperdicio de memoria. Es decir, esta memoria está fuera del control de gc

Contando referencias

Debido a que todo en Python es un objeto, todas las variables que ve son esencialmente un puntero al objeto. Cuando un objeto ya no se llama, es decir, cuando el recuento de referencia del objeto (número de puntero) es 0, significa que el objeto nunca es accesible, naturalmente se convierte en basura y necesita ser reciclado. Puede entenderse simplemente que no hay ninguna variable para señalarlo.

import os  
 import psutil   
 
# Tamaño de la memoria ocupada por el programa actual pitón
 
def show_memory_info (pista):  
    pid = os.getpid ()  
    p = psutil.Process (pid)  
    info = p.memory_full_info ()  
    memoria = info.uss / 1024./ 1024  
 print ({} memoria utilizada: {} MB .format (pista, memoria))

 

Se puede ver que se llama a la función func (). Después de crear la lista a, el uso de la memoria aumenta rápidamente a 433 MB: después de que finaliza la llamada a la función, la memoria vuelve a la normalidad. Esto se debe a que la lista declarada dentro de la función es una variable local. Después de que la función regrese, la referencia de la variable local se cancelará; en este momento, el número de referencia del objeto al que hace referencia la lista a es 0, y Python realizará la recolección de basura, por lo que La gran cantidad de memoria ocupada anteriormente está de vuelta.

def func ():  
    show_memory_info ( 
 inicial  
)  
global a 
    a = [i para   i en el   rango (10000000 )]  
    show_memory_info (después de un creado) 
func ()  
show_memory_info ( 
 terminado  
) 
# ######### Salida ##########   
memoria inicial utilizada: 48.88671875 MB  
después de usar una memoria creada: 433.94921875 MB  
memoria final utilizada: 433.94921875 MB

 

En el nuevo código, global a significa declarar a como una variable global. Luego, incluso después de que la función regrese, la referencia de la lista todavía existe, por lo que el objeto no se recolectará basura y aún consumirá mucha memoria. Del mismo modo, si devolvemos la lista generada y la recibimos en el programa principal, la referencia aún existe, la recolección de basura no se activará y todavía queda mucha memoria:

def func ():  
    show_memory_info (inicial)  
    a = [i para   i en   desorden (10000000 )]  
    show_memory_info (después de un creado) 
 
devolver un  
a = func ()
show_memory_info (terminado) 
 
# ######### Salida ##########   
memoria inicial utilizada: 47.96484375 MB
después de usar una memoria creada: 434.515625 MB
memoria final utilizada: 434.515625 MB

 

¿Cómo puede ver cuántas veces se hace referencia a la variable? A través de sys.getrefcount

sys de   importación
A = []  
 # referencias dos veces, una a partir de una, de un getrefcount 
Imprimir (sys.getrefcount (A))  
 
DEF FUNC (A):  
 # cuatro referencias, una función de Python llamada pila, los parámetros de función, y getrefcount   
impresión (sys.getrefcount (A))  
func (a)  
# Dos referencias, uno de un, de un getrefcount, llame a la función func no existe   
impresión (sys.getrefcount (A))   
 # ######### ########## salida  
2  
4 4  
2 

 

Si involucra una llamada de función, agregará dos adicionales 1. Pila de funciones 2. Llamada de funciones

Desde aquí, podemos ver que Python ya no necesita liberar memoria como C, pero Python también nos proporciona un método para liberar memoria manualmente gc.collect ()

importar gc  
show_memory_info (inicial)  
a = [i para   i en el rango (10000000 )]  
show_memory_info (después de un creado) 
del a 
gc.collect () 
show_memory_info (terminar)  
print (a)  
 # ######### 输出 ########## 
memoria inicial utilizada: 48.1015625 MB
después de usar una memoria creada: 434.3828125 MB  
memoria final utilizada: 48.33203125 MB 
 -------------------------------------------- ------------------------------- 
NameErrorTraceback (última llamada más reciente) 
 
 en  
11  
12 show_memory_info (terminar) 
 ---> 13 imprimir (a)
 
NameError: el nombre a no está definido 

 

A partir de ahora, parece que el mecanismo de recolección de basura de Python es muy simple. Siempre que el número de referencias de objeto sea 0, gc debe activarlo. ¿Es el número de referencia 0 una condición suficiente y necesaria para activar gc?

Reciclaje

Si hay dos objetos que se refieren entre sí y ya no son referenciados por otros objetos, ¿deberían ser basura recolectada?

def func ():
    show_memory_info (inicial)  
    a = [i para   i en el   rango (10000000 )]
    b = [i para   i en el   rango (10000000 )]  
    show_memory_info (después de crear a, b)  
    a.append (b)  
    b. apéndice (a) 
func () 
show_memory_info (terminado)  
# ######### Salida ##########   
memoria inicial utilizada: 47.984375 MB  
después de a, b creó la memoria utilizada: 822.73828125 MB  
memoria final utilizada:   821.73046875 MB

 

Es obvio por los resultados que no se han recuperado, pero desde un punto de vista de procedimiento, cuando esta función finaliza, ayb como variables locales ya no existen en el sentido del programa. Pero debido a su referencia mutua, sus números de referencia no son cero. Cómo evitarlo en este momento

    1. Rectifique el código lógicamente para evitar tales referencias circulares
    2. Mediante reciclaje manual
import gc 
 def func ():  
    show_memory_info (inicial)  
    a = [i para   i en el   rango (10000000 )]  
    b = [i para   i en el   rango (10000000 )]
    show_memory_info (después de crear a, b)  
    a.append (b) 
    b. apéndice (a) 
func () 
gc.collect () 
show_memory_info (terminado)  
# ######### Salida ##########   
memoria inicial utilizada: 49.51171875 MB  
después de a, b creó la memoria utilizada: 824.1328125 MB  
memoria final utilizada: 49.98046875 MB

 

Para referencias circulares, Python tiene su algoritmo automático de recolección de basura 1. Algoritmo de barrido de marca 2. Recolección generacional

Marca clara

Los pasos para borrar la marca se resumen de la siguiente manera: 1. El GC marcará todos los "objetos activos" 2. Recicla aquellos objetos que no están marcados como "objetos inactivos" Entonces, ¿cómo determina Python qué es un objeto inactivo? Comprender el concepto de inalcanzable. Para un gráfico dirigido, si comenzamos a atravesar desde un nodo y marcamos todos los nodos por los que pasa, luego, después de que termina el recorrido, todos los nodos que no han sido marcados, lo llamamos nodos inalcanzables. Obviamente, la existencia de estos nodos no tiene sentido. Naturalmente, necesitamos reciclarlos. Pero atravesar el gráfico completo cada vez es una gran pérdida de rendimiento para Python. Por lo tanto, en la implementación de recolección de basura de Python, mark-sweep mantiene una estructura de datos usando una lista doblemente vinculada, y solo considera objetos de tipo contenedor (solo los objetos tipo contenedor, list, dict, tuple, instancia, pueden tener referencias circulares) .

Reciclaje de "basura" de Python Reciclaje de "basura" de Python

En la figura, el círculo negro pequeño se considera como una variable global, es decir, se considera como el objeto raíz. Desde el círculo negro pequeño, se puede alcanzar directamente el objeto 1, luego se marcará, y los objetos 2, 3 se pueden alcanzar indirectamente y se marcarán, y 4 Si no es accesible con 5, entonces 1, 2, 3 son objetos activos, 4 y 5 son objetos inactivos y serán reciclados por GC.

Reciclaje generacional

El reciclaje generacional es un método de operación de espacio por tiempo. Python divide la memoria en diferentes conjuntos según el tiempo de supervivencia de los objetos. Cada conjunto se denomina generación. Python divide la memoria en 3 "generaciones", que son generaciones jóvenes. (0a generación), media generación (1ra generación) y vieja generación (2da generación), corresponden a tres listas vinculadas, y su frecuencia de recolección de basura disminuye con el aumento del tiempo de supervivencia del objeto. Los objetos recién creados se asignarán en la generación joven. Cuando el número total de listas vinculadas de la generación joven alcance el límite superior (cuando los objetos nuevos menos los objetos eliminados en el recolector de basura alcancen el umbral correspondiente), se activará el mecanismo de recolección de basura de Python para poner Los objetos reciclados se reciclan, y los que no se reciclan se trasladarán a la edad media, y así sucesivamente. Los objetos en la vejez son los objetos más longevos e incluso sobreviven al ciclo de vida completo del sistema. . Al mismo tiempo, el reciclaje generacional se basa en la tecnología de eliminación de marcas. De hecho, el reciclaje generacional se basa en la idea de que los objetos recién nacidos tienen más probabilidades de ser recolectados de basura, y los objetos que viven más tienen una mayor probabilidad de continuar sobreviviendo. Por lo tanto, a través de este enfoque, puede guardar muchos cálculos, mejorando así el rendimiento de Python. Entonces, para la pregunta ahora, el recuento de referencias es solo una condición no esencial suficiente para activar gc, y las referencias circulares también se activarán.

Depurar

Puede usar objgraph para depurar el programa, ya que su documentación oficial no se ha leído cuidadosamente, solo puede poner la documentación aquí para su referencia ~ Dos de las funciones son muy útiles 1. show_refs () 2. show_backrefs ()

Texto original de: https://developer.51cto.com/art/201912/607082.htm

La dirección de este artículo: https://www.linuxprobe.com/python-garbage-collection.html

Supongo que te gusta

Origin www.cnblogs.com/Ph-one/p/12737869.html
Recomendado
Clasificación