Análisis del mecanismo de recolección de basura de Python

¡La sección del video tutorial de Python analizará el mecanismo de recolección de basura de Python hoy!

1. Recolección de basura El
contador de referencia es el principal y la recolección de subcódigos y la eliminación de marcas son secundarias.

1.1 El gran administrador refchain
tiene una lista circular doblemente vinculada llamada refchain en el código fuente de Python C. Esta lista vinculada es bastante impresionante, porque una vez que se crea un objeto en el programa de Python, se agregará a la lista vinculada de refchain. En otras palabras, guarda todos los objetos.

1.2 Contador de referencia
En todos los objetos de refchain hay un ob_refcnt para guardar el contador de referencia del objeto actual, como su nombre lo indica, es el número de veces que se ha referenciado.
Cuando se hace referencia al valor varias veces, los datos no se crearán repetidamente en la memoria, sino el contador de referencia +1. Cuando se destruye el objeto, el contador de referencia será -1 al mismo tiempo. Si el contador de referencia es 0, el objeto será eliminado de la lista de refchain y destruido en la memoria al mismo tiempo (sin considerar circunstancias especiales como almacenamiento en caché).

age = 18number = age  # 对象18的引用计数器 + 1del age          # 对象18的引用计数器 - 1def run(arg):

    print(arg)

run(number)   # 刚开始执行函数时,对象18引用计数器 + 1,当函数执行完毕之后,对象18引用计数器 - 1 。num_list = [11,22,number] # 对象18的引用计数器 + 1复制代码

1.3 Eliminación de marcas y recolección generacional La
recolección de basura basada en el contador de referencias es muy conveniente y simple, pero aún tiene el problema de las referencias circulares, lo que hace que sea imposible recolectar algunos datos con normalidad, como:

v1 = [11,22,33]        # refchain中创建一个列表对象,由于v1=对象,所以列表引对象用计数器为1.v2 = [44,55,66]        # refchain中再创建一个列表对象,因v2=对象,所以列表对象引用计数器为1.v1.append(v2)        # 把v2追加到v1中,则v2对应的[44,55,66]对象的引用计数器加1,最终为2.v2.append(v1)        # 把v1追加到v1中,则v1对应的[11,22,33]对象的引用计数器加1,最终为2.del v1    # 引用计数器-1del v2    # 引用计数器-1复制代码

Para el código anterior, encontrará que después de la operación del, ninguna variable usará los dos objetos de la lista, pero debido al problema de referencia circular, su contador de referencia no es 0, por lo que su estado: nunca usado, tampoco será destruido. Si este tipo de código es demasiado en el proyecto, hará que la memoria se consuma hasta que se agote y el programa se bloquee.
Para solucionar el problema de las referencias circulares se introduce la tecnología de eliminación de marcas para tratar objetos que pueden tener referencias circulares.Los tipos de aplicaciones circulares que pueden existir son: listas, tuplas, diccionarios, colecciones, clases personalizadas, etc. El tipo de anidamiento de datos.
Eliminación de marcas: cree una lista vinculada especial para guardar objetos como listas, tuplas, diccionarios, colecciones, clases personalizadas, etc., y luego verifique si hay referencias circulares en los objetos en esta lista vinculada, y si las hay, deje que el los contadores de referencia de ambas partes sean -1.

Colección generacional: Optimizar la lista enlazada en eliminación de marcas, dividir aquellos objetos que puedan tener referencia a 3 listas enlazadas, la lista enlazada se llama: 0/1/2 tres generaciones, cada generación puede almacenar objetos y umbrales, cuando llega a Cuando el se establece el umbral, cada objeto en la lista vinculada correspondiente se escaneará una vez, excepto que las referencias circulares se reducen en 1 y los objetos cuyo contador de referencia es 0 se destruyen.

// 分代的C源码#define NUM_GENERATIONS 3struct gc_generation generations[NUM_GENERATIONS] = {    /* PyGC_Head,                                    threshold,    count */

    {
    
    {
    
    (uintptr_t)_GEN_HEAD(0), (uintptr_t)_GEN_HEAD(0)},   700,        0}, // 0代

    {
    
    {
    
    (uintptr_t)_GEN_HEAD(1), (uintptr_t)_GEN_HEAD(1)},   10,         0}, // 1代

    {
    
    {
    
    (uintptr_t)_GEN_HEAD(2), (uintptr_t)_GEN_HEAD(2)},   10,         0}, // 2代};复制代码

Atención especial: los significados de umbral y recuento de generación 0 y generación 1 y 2 son diferentes.

Generación 0, el recuento representa el número de objetos en la lista vinculada de generación 0 y el umbral representa el umbral del número de objetos en la lista vinculada de generación 0. Si se excede, se realizará una comprobación de exploración de generación 0. En la generación 1, el recuento representa el número de exploraciones de la lista enlazada de la generación 0 y el umbral representa el umbral del número de exploraciones de la lista enlazada de la generación 0. Si supera, se realiza una comprobación de la exploración de la generación 1. En la segunda generación, el recuento representa el número de exploraciones de la lista enlazada de primera generación y el umbral representa el umbral del número de exploraciones de la lista enlazada de primera generación. Si supera, se realiza una comprobación de exploración de segunda generación.

1.4 Simulación de escenarios
Explique el proceso detallado de administración de memoria y recolección de basura basado en la parte inferior del lenguaje C y combinado con diagramas.

El primer paso: cuando se crea el objeto age = 19, el objeto se agregará a la lista de refchain.

Paso 2: Cuando se crea el objeto num_list = [11,22], el objeto de lista se agregará a refchain y las generaciones 0.

Paso 3: Cuando el objeto recién creado hace que el número de objetos en la lista vinculada de generación 0 sea mayor que el umbral 700, los objetos en la lista vinculada deben ser escaneados y verificados.

Cuando la generación 0 es mayor que el umbral, la capa inferior no escanea directamente la generación 0, sino que primero determina si 2 y 1 también superan el umbral.

Si la generación 2 y la generación 1 no alcanzan el umbral, escanee la generación 0 y deje que el recuento + 1 de la generación 1.
Si la segunda generación ha alcanzado el umbral, las tres listas enlazadas de 2, 1 y 0 se empalmarán juntas para un escaneo completo, y el recuento de las generaciones
2, 1 y 0 se restablecerá a 0. Si la generación 1 ha alcanzado el umbral, luego 1, 0 Las dos listas enlazadas se empalman para escanear, y los recuentos de todas las generaciones 1 y 0 se restablecen a 0.
Al escanear las listas enlazadas empalmadas, el propósito principal es eliminar las referencias circulares y destruir la basura. El proceso detallado es:

Escanee la lista vinculada, copie el contador de referencia de cada objeto y guárdelo en gc_refs para proteger el contador de referencia original.
Escanee cada objeto en la lista vinculada nuevamente, y verifique si hay una referencia circular, y si existe, reduzca los respectivos gc_refs en 1.
Escanee la lista vinculada nuevamente y mueva los objetos cuyo gc_refs es 0 a la lista vinculada inalcanzable; los objetos que no son 0 se actualizan directamente a la lista vinculada de próxima generación.
Trate con el destructor y las referencias débiles de los objetos en la lista enlazada inalcanzable.Los objetos que no se pueden destruir se actualizan a la lista enlazada de próxima generación, y los que se pueden destruir permanecen en esta lista enlazada. El destructor se refiere a los objetos que definen el método __del__, que debe ejecutarse antes de ser destruido.
Finalmente, cada objeto en inalcanzable se destruye y se elimina de la lista de refchain (independientemente del mecanismo de almacenamiento en caché).
En este punto, el proceso de recolección de basura ha terminado.

1.5 Mecanismo de almacenamiento en caché
Como puede aprender de lo anterior, cuando el contador de referencia de un objeto es 0, se destruirá y se liberará la memoria. De hecho, no es tan simple y grosero, porque la creación y destrucción repetidas hará que la eficiencia de ejecución del programa sea menor. El mecanismo del "mecanismo de caché" se introduce en Python.

Por ejemplo: cuando el contador de referencia es 0, el objeto no se destruirá, sino que se colocará en una lista vinculada llamada free_list. Cuando el objeto se cree más tarde, la memoria no se volverá a abrir, pero el objeto anterior se en free_list Ven y restablece el valor interno para usar.

El tipo flotante, la lista enlazada free_list mantenida, puede almacenar en caché hasta 100 objetos flotantes.

v1 = 3.14    # 开辟内存来存储float对象,并将对象添加到refchain链表。

print( id(v1) ) # 内存地址:4436033488

del v1    # 引用计数器-1,如果为0则在rechain链表中移除,不销毁对象,而是将对象添加到float的free_list.

v2 = 9.999    # 优先去free_list中获取对象,并重置为9.999,如果free_list为空才重新开辟内存。

print( id(v2) ) # 内存地址:4436033488

# 注意:引用计数器为0时,会先判断free_list中缓存个数是否满了,未满则将对象缓存,已满则直接将对象销毁。复制代码

El tipo int no se basa en free_list, pero mantiene una lista enlazada small_ints para almacenar datos comunes (pequeño grupo de datos), el rango del pequeño grupo de datos: -5 <= valor <257. Es decir: cuando se reutilizan los enteros de este rango, la memoria no se volverá a abrir.

v1 = 38    # 去小数据池small_ints中获取38整数对象,将对象添加到refchain并让引用计数器+1print( id(v1))  #内存地址:4514343712

v2 = 38 # 去小数据池small_ints中获取38整数对象,将refchain中的对象的引用计数器+1print( id(v2) ) #内存地址:4514343712

# 注意:在解释器启动时候-5~256就已经被加入到small_ints链表中且引用计数器初始化为1,

# 代码中使用的值时直接去small_ints中拿来用并将引用计数器+1即可。另外,small_ints中的数据引用计数器永远不会为0

# (初始化时就设置为1了),所以也不会被销毁。复制代码

El tipo str mantiene la lista enlazada unicode_latin1 [256] e internamente almacena en caché todos los caracteres ascii, y no se creará repetidamente cuando se use en el futuro.

v1 = "A"

print( id(v1) ) # 输出:4517720496

del v1

v2 = "A"

print( id(v1) ) # 输出:4517720496

# 除此之外,Python内部还对字符串做了驻留机制,针对只含有字母、数字、下划线的字符串(见源码Objects/codeobject.c),如果

# 内存中已存在则不会重新在创建而是使用原来的地址里(不会像free_list那样一直在内存存活,只有内存中有才能被重复利用)。

```c
v1 = "asdfg"

v2 = "asdfg"

print(id(v1) == id(v2)) # 输出:True复制代码

list类型,维护的free_list数组最多可缓存80个list对象。


```c
v1 = [11,22,33]

print( id(v1) ) # 输出:4517628816del v1

v2 = ["你","好"]

print( id(v2) ) # 输出:4517628816复制代码

El tipo de tupla mantiene una matriz free_list con una capacidad de matriz de 20. Los elementos de la matriz pueden ser listas vinculadas y cada lista vinculada puede contener hasta 2000 objetos de tupla. Cuando la matriz de tuplas free_list almacena datos, la lista vinculada correspondiente en la matriz free_list se encuentra de acuerdo con el número que la tupla puede contener como índice, y se agrega a la lista vinculada.

v1 = (1,2)

print( id(v1) )del v1  # 因元组的数量为2,所以会把这个对象缓存到free_list[2]的链表中。v2 = ("哈哈哈","Alex")  # 不会重新开辟内存,而是去free_list[2]对应的链表中拿到一个对象来使用。print( id(v2) )复制代码

tipo dict, la matriz free_list mantenida puede almacenar en caché hasta 80 objetos dict

v1 = {
    
    "k1":123}

print( id(v1) )  # 输出:4515998128

del v1

v2 = {
    
    "name":"哈哈哈","age":18,"gender":"男"}

print( id(v1) ) # 输出:4515998128复制代码

Análisis inferior del código fuente del lenguaje C
Este artículo proviene del sitio web chino de php: video tutorial de Python https://www.php.cn/course/list/30.html

Supongo que te gusta

Origin blog.csdn.net/Anna_xuan/article/details/109581479
Recomendado
Clasificación