Mecanismo gráfico de recolección de basura de Python

El módulo GC de Python utiliza principalmente "conteo de referencias" para rastrear y recolectar basura. Sobre la base del recuento de referencias, también puede usar la marca y el barrido para resolver el problema de las referencias circulares que pueden generar los objetos de contenedor. Mediante la "recolección de generación" (recolección de generación) a cambio de espacio para mejorar aún más la eficiencia de la recolección de basura.

Mecanismo de conteo de referencias:
todo en Python es un objeto, y su núcleo es una estructura: PyObject

typedef struct_object { 
    int ob_refcnt; 
    struct_typeobject * ob_type; 
} PyObject;

  PyObject es un contenido imprescindible para cada objeto, donde ob_refcnt se usa como un recuento de referencia. Cuando un objeto tiene una nueva referencia, su ob_refcnt aumentará, cuando el objeto que hace referencia a él se elimine, su ob_refcnt disminuirá

#define Py_INCREF (op) ((op) -> ob_refcnt ++) // Incremento del recuento 
#define Py_DECREF (op) \ // Disminuye el recuento         
     if (- (op) -> ob_refcnt! = 0) \ 
         ; \ 
     else \ 
         __Py_Dealloc ( (PyObject *) (op))

  Cuando el recuento de referencia es 0, la vida del objeto termina.
Las ventajas del mecanismo de recuento de referencias:
1. Simple
2. Tiempo real: una vez que no hay referencia, la memoria se libera directamente. No hay necesidad de esperar hasta un momento específico como otros mecanismos. El tiempo real también trae un beneficio: el tiempo para procesar la memoria recuperada se asigna al tiempo habitual.
Desventajas del mecanismo de conteo de referencias:
1. Mantener el conteo de referencias consume recursos
2. Referencias circulares

lista1 = [] 
lista2 = [] 
lista1.append (lista2) 
lista2.append (lista1)

  

Lista1 y lista2 se refieren entre sí. Si no hay ninguna referencia de otro objeto a ellas, el recuento de referencias de lista1 y lista2 sigue siendo 1, y la memoria ocupada nunca puede recuperarse, lo que será fatal.
Para el potente hardware de hoy, la deficiencia 1 sigue siendo aceptable, pero las referencias circulares conducen a pérdidas de memoria, y Python condenado también introducirá un nuevo mecanismo de reciclaje.
Como se mencionó anteriormente, el mecanismo de reciclaje en Python se basa principalmente en el recuento de referencias, y los dos mecanismos de barrido de marcas y recolección generacional se complementan.

1. Mecanismo de marca clara

El mecanismo de marcar y barrer, como su nombre lo indica, primero marca el objeto (detección de basura) y luego limpia la basura (recolección de basura). Como se muestra:

 

 

Primero, todos los objetos se marcan inicialmente como blancos, y se determinan los objetos del nodo raíz (estos objetos no se eliminarán), y se marcan como negros (lo que indica que los objetos son válidos). Marque los objetos referenciados por los objetos válidos como grises (lo que indica que los objetos son accesibles, pero los objetos a los que se refieren no han sido verificados), y después de verificar los objetos referenciados por los objetos grises, marque el gris como negro. Repita hasta que no haya nodos grises. Finalmente, los nodos blancos son todos los objetos que necesitan ser borrados.

2. Organización de objetos de reciclaje.

El mecanismo de alto nivel adoptado aquí se utiliza como un mecanismo auxiliar de conteo de referencias para resolver el problema de las referencias circulares. La referencia circular solo aparecerá en "hay un objeto al que se puede hacer referencia a otros objetos", como: lista, clase, etc.

Para organizar estos objetos de reciclaje, se debe establecer una lista vinculada. Naturalmente, cada objeto recolectado necesita proporcionar más información. El siguiente código es inevitable en el objeto de reciclaje.

/ * La información del GC se almacena ANTES de la estructura del objeto. * / 
typedef union _gc_head { 
    struct { 
        union _gc_head * gc_next; 
        union _gc_head * gc_prev; 
        Py_ssize_t gc_refs; 
    } gc; 
    maniquí doble largo; / * forzar la alineación del peor de los casos * / 
} PyGC_Head;

  La estructura real de un objeto se muestra en la figura:

 

 

El puntero a través de PyGC_Head conecta cada objeto recuperado para formar una lista vinculada, es decir, todos los objetos inicializados mencionados en 1.

3. Tecnología generacional

La tecnología generacional es una tecnología típica del tiempo al espacio, que también es la tecnología clave en Java. La idea simple es que cuanto más tiempo exista el objeto, más probable es que no sea basura, y menos se debe recolectar.

Esta idea puede reducir las operaciones adicionales generadas por el mecanismo de marcado y barrido. La generación consiste en dividir el objeto de reciclaje en varias generaciones, cada generación es una lista vinculada (colección), el momento para la generación de marcas claras y los objetos de generación

El tiempo de supervivencia es proporcional.

/ *** Estado global de GC *** / 
 
 struct gc_generation { 
     PyGC_Head head; 
     umbral int; / * umbral de colección * / 
     int count; / * recuento de asignaciones o colecciones de 
                   generaciones más jóvenes * / 
 }; // 每个 代 的 结构
 
 #define NUM_GENERATIONS 3 // 代 的 个数
 #define GEN_HEAD (n) (& generations [n] .head) 
 
 / * listas vinculadas de objetos contenedor * / 
 estructura estática gc_generation generaciones [NUM_GENERATIONS] = { 
     / * PyGC_Head, umbral, recuento * / 
     {{{GEN_HEAD (0), GEN_HEAD (0), 0}}, 700, 0},  
     {{{GEN_HEAD (1 ), GEN_HEAD (1), 0}}, 10, 0},
     {{{GEN_HEAD (2 ), GEN_HEAD (2), 0}}, 10, 0}, 
 };
 
 PyGC_Head * _PyGC_generation0 = GEN_HEAD (0);

  

Se puede ver en el código anterior que hay tres generaciones en python, y el valor umbral de cada generación indica el número máximo de objetos que la generación puede acomodar. Por defecto, cuando la generación 0 excede de 700, o la generación 1, 2 excede de 10, se activará el mecanismo de recolección de basura.

El activador de generación 0 limpiará las tres generaciones, el activador de generación 1 limpiará 1, 2 generaciones, y el activador de segunda generación solo se limpiará solo.

Este es un proceso completo de recolección: creación de listas vinculadas, determinación del nodo raíz, marcado de basura, recolección de basura ~

1. El establecimiento de una lista vinculada
Primero, dijo Zhongli en tecnología generacional: el disparador de 0 generaciones limpiará las tres generaciones, el disparador de 1 generación limpiará 1, 2 generaciones y 2 generaciones solo se limpiarán a sí mismas. Al limpiar la generación 0, se vincularán tres listas vinculadas (generaciones), y al limpiar la generación 1, se vincularán dos o dos generaciones. Los siguientes tres pasos se dirigen a la lista vinculada después del establecimiento.

2. Determine el nodo raíz La
figura 1 es un ejemplo. List1 y list2 tienen referencias circulares, y list3 y list4 tienen referencias circulares. a es una referencia externa.

 

 

Para dicha lista vinculada, ¿cómo obtenemos el nodo raíz? En Python, se propone un concepto de conteo de referencia efectivo sobre la base del conteo de referencia. Como su nombre lo indica, el recuento de referencia efectivo es el recuento después de eliminar la referencia circular.

Aquí está el código relevante para calcular el recuento de referencia efectivo:

  / * Establecer todo gc_refs = ob_refcnt. Después de esto, gc_refs es> 0 para todos los objetos 
  * en contenedores, y es GC_REACHABLE para todos los objetos gc rastreados que no están en 
  * contenedores. 
  * / 
 static void 
 update_refs (PyGC_Head * container) 
 { 
     PyGC_Head * gc = container-> gc.gc_next; 
     para (; gc! = contenedores; gc = gc-> gc.gc_next) { 
         afirmar (gc-> gc.gc_refs == GC_REACHABLE); 
         gc-> gc.gc_refs = Py_REFCNT (FROM_GC (gc)); 
         afirmar (gc-> gc.gc_refs! = 0); 
     } 
 } 
 
 / * Una devolución de llamada transversal para subtract_refs. * / 
 static int 
 visit_decref (PyObject * op, void * data) 
 { 
     afirmar (op! = NULL);
     if (PyObject_IS_GC (op)) { 
         PyGC_Head * gc = AS_GC (op); 
         / * Solo estamos interesados ​​en gc_refs para los objetos de la 
          * generación que se recopila, lo que puede reconocerse 
          * porque solo ellos tienen gc_refs positivos. 
          * / 
         afirmar (gc-> gc.gc_refs! = 0); / * else refcount era demasiado pequeño * / 
         if (gc-> gc.gc_refs> 0) 
             gc-> gc.gc_refs--; 
     } 
     devuelve 0; 
 } 
 
 / * Resta referencias internas de gc_refs. Después de esto, gc_refs es> = 0 
  * para todos los objetos en contenedores, y es GC_REACHABLE para todos los 
  objetos gc * rastreados que no están en contenedores. Los que tienen gc_refs> 0 son directamente 
  * accesibles desde contenedores externos, por lo que no se puede recolectar. 
  * / 
 vacío estático
 subtract_refs (PyGC_Head * contenedores) 
 { 
     traverseproc traverse; 
     PyGC_Head * gc = contenedores-> gc.gc_next; 
     para (; gc! = contenedores; gc = gc-> gc.gc_next) { 
         traverse = Py_TYPE (FROM_GC (gc)) -> tp_traverse; 
         (vacío) transversal (FROM_GC (gc), 
                        (visitproc) visit_decref, 
                        NULL); 
     } 
 }

  

Se crea una copia de la referencia en la función update_refs.

La función visit_decref disminuye la copia de la referencia en 1. La función de transversal en la función de subtract_refs es recorrer cada referencia en el objeto y realizar la operación visit_decref.

Finalmente, el objeto cuya copia de conteo de referencia no es 0 en la lista vinculada es el nodo raíz.

Descripción:

1. ¿Por qué crear una copia de referencia?

Respuesta: Este proceso es el proceso de encontrar el nodo raíz. En este momento, no es apropiado modificar el conteo. subtract_refs realiza una operación visit_decref en el objeto referenciado del objeto. Si los objetos en la lista vinculada se refieren a objetos fuera de la lista vinculada, el recuento de objetos fuera de la lista vinculada se reducirá. Obviamente, es muy probable que este objeto sea reciclado, y el mecanismo de reciclaje no debe tratar con objetos que no sean de reciclaje.

2. Preguntas de transversal (sin resolver)?

Respuesta: Al principio, había una pregunta. En el ejemplo anterior, el resultado del procesamiento de list1 en la función subtract_refs debería ser el siguiente:

 

 

Entonces gc apunta a la lista 2. En este momento, la copia de la lista2 (0) no disminuirá, pero todavía hay una referencia real a la lista1, entonces ¿la copia de la lista1 se reducirá en 1? Obviamente, hay un problema si menos 1.

Por lo tanto, cuando list1 es 0, la poligonal no procesará las referencias de list1 (o list2 no tiene referencia nominal a list1).

En este momento, hay otro problema, si hay un objeto externo b, referencia a list2, después de procesar list1 en la función subtract_refs, como se muestra a continuación:

 

 

Cuando la función list_refs atraviesa list2, ¿la copia de list2 se reducirá en 1? Obviamente, el papel de la poligonal aún no se comprende.

3. Marca de basura
A continuación, Python crea dos listas vinculadas, una almacena el nodo raíz y el objeto de referencia del nodo raíz. El otro almacena objetos inalcanzables.

El método de marcado es la idea de marcado en Zhongli. El código es el siguiente:

/ * Una devolución de llamada transversal para move_unreachable. * / 
  static int 
  visit_reachable (PyObject * op, PyGC_Head * accesible) 
  { 
      if (PyObject_IS_GC (op)) { 
          PyGC_Head * gc = AS_GC (op); 
          const Py_ssize_t gc_refs = gc-> gc.gc_refs; 
  
          if (gc_refs == 0) { 
              / * Esto está en la lista 'joven' de move_unreachable, pero 
               * el recorrido aún no ha llegado a él. Todo lo que 
               * tenemos que hacer es decirle a move_unreachable que es 
               * accesible. 
               * / 
              gc-> gc.gc_refs = 1; 
          } 
          else if (gc_refs == GC_TENTATIVELY_UNREACHABLE) {
              / * Esto tenía gc_refs = 0 cuando move_unreachable llegó 
               * a él, pero resulta que es accesible después de todo. 
               * Vuelva a moverlo a la lista 'joven' de 
               move_unreachable , * y move_unreachable eventualmente lo alcanzará 
               * nuevamente. 
               * / 
              gc_list_move (gc, accesible); 
              gc-> gc.gc_refs = 1; 
          } 
          / * De lo contrario, no hay nada que hacer. 
           * Si gc_refs> 0, debe estar en la lista 'young' 
           * de move_unreachable, y move_unreachable eventualmente lo alcanzará. 
           * Si gc_refs == GC_REACHABLE, está en algún otro
           * generación, por lo que no nos importa, o move_unreachable 
           * ya se ha ocupado de eso. 
           * Si gc_refs == GC_UNTRACKED, 
           * / 
           else { 
              afirmar (gc_refs> 0 
                     || gc_refs == GC_REACHABLE 
                     || gc_refs == GC_UNTRACKED); 
           } 
      } 
      devuelve 0; 
  } 
  
  / * Mueve los objetos inalcanzables de jóvenes a inalcanzables. Después de esto, 
   * todos los objetos en young tienen gc_refs = GC_REACHABLE, y todos los objetos en 
   * inalcanzable tienen gc_refs = GC_TENTATIVELY_UNREACHABLE. Todos los 
   objetos rastreados * gc que no están en estado joven o inalcanzable aún tienen gc_refs = GC_REACHABLE. 
   * Todos los objetos en los jóvenes después de esto son accesibles directa o indirectamente
   * desde fuera del joven original; y todos los objetos en inalcanzable son 
   * no. 
   * / 
  static void 
  move_unreachable (PyGC_Head * young, PyGC_Head * inalcanzable) 
  { 
      PyGC_Head * gc = young-> gc.gc_next; 
  
      / * Invariantes: todos los objetos "a la izquierda" de nosotros en young tienen gc_refs 
       * = GC_REACHABLE, y de hecho son accesibles (directa o indirectamente) 
       * desde fuera de la lista de young como estaba en la entrada. Todos los demás objetos 
       * del joven original "a la izquierda" de nosotros están ahora inalcanzables, 
       * y tienen gc_refs = GC_TENTATIVELY_UNREACHABLE. Todos los objetos a la 
       izquierda de nosotros en 'joven' ahora han sido escaneados, y no hay objetos aquí
       * o a la derecha han sido escaneados todavía. 
       * / 
  
      while (gc! = young) { 
          PyGC_Head * next; 
  
          if (gc-> gc.gc_refs) {  
              / * gc es definitivamente accesible desde fuera del 
               * original 'joven'. Márcalo como tal y atraviesa
               * sus punteros para encontrar cualquier otro objeto que pueda 
               * ser accesible directamente desde él. Tenga en cuenta que la 
               * llamada a tp_traverse puede agregar objetos a young, 
               * por lo que tenemos que esperar hasta que regrese para determinar 
               * el siguiente objeto a visitar. 
               * / 
              PyObject * op = FROM_GC (gc); 
              traverseproc traverse = Py_TYPE (op) -> tp_traverse; 
              afirmar (gc-> gc.gc_refs> 0); 
              gc-> gc.gc_refs = GC_REACHABLE;
              (void) traverse (op, 
                              (visitproc) visit_reachable, 
                              (void *) young); 
              siguiente = gc-> gc.gc_next; 
          }
          sino { 
              / * Esto * puede * ser inalcanzable. Para avanzar, 
               * asuma que lo es. gc no es directamente accesible desde 
               * ningún objeto que ya hayamos atravesado, pero puede ser 
               * accesible desde un objeto que aún no hemos alcanzado. 
               * visit_reachable eventualmente moverá gc nuevamente a 
               * young si es así, y lo veremos nuevamente. 
               * / 
              next = gc-> gc.gc_next; 
              gc_list_move (gc, inalcanzable);
              gc-> gc.gc_refs = GC_TENTATIVELY_UNREACHABLE; 
          } 
          gc = siguiente; 
      } 
  }

  

 

 

Después de marcar, la lista vinculada es como se muestra arriba.

4.
El proceso de recolección de basura es destruir los objetos en la lista vinculada inalcanzable. El siguiente código es cómo borrar la lista:

/ * Métodos * / 
 
 static void 
 list_dealloc (PyListObject * op) 
 { 
     Py_ssize_t i; 
     PyObject_GC_UnTrack (op); 
     Py_TRASHCAN_SAFE_BEGIN (op) 
     if (op-> ob_item! = NULL) { 
         / * Hazlo al revés, para Christian Tismer. 
            Hay un caso de prueba simple donde de alguna manera esto reduce la 
            agitación cuando se crea una lista * muy * grande y 
            se elimina inmediatamente. * / 
         i = Py_SIZE (op); 
         while (--i> = 0) { 
             Py_XDECREF (op-> ob_item [i]); 
         } 
         PyMem_FREE (op-> ob_item); 
     } 
     if (numfree <PyList_MAXFREELIST && PyList_CheckExact (op))
         free_list [numfree ++] = op; 
     más
         Py_TYPE (op) -> tp_free ((PyObject *) op); 
     Py_TRASHCAN_SAFE_END (op) 
 }

  

Hoja de ruta de aprendizaje de Python https://www.bilibili.com/video/BV1V741117Zt/

Dirección de empleo de Python y perspectivas de carrera https://www.bilibili.com/video/BV1ut4y1U7bM/

Supongo que te gusta

Origin www.cnblogs.com/guran0823/p/12712439.html
Recomendado
Clasificación