Número 83
7 de febreiro de 2024

Recollida do lixo

Na última Folla mencionei dúas maneiras de xestionar a memoria reservada na morea: liberala manualmente ou facer recollida de lixo.

A recollida do lixo non ten maxia ningunha. Cando vivía na Coruña só tiña que botalo nun colector na rúa e todas as noites viña un camión cun brazo hidráulico que recollía o colector e envorcaba o seu contido nun compactador. En Nova York tiña aínda menos: eu deixaba o lixo nunha bolsa na rúa dúas veces por semana e despois viñan dous homes que collían a bolsa a pulso e a botaban no camión.

Un mago olla mentres un operario do lixo se achega a el tirando dun colector.“Tráioche algo que preciso de facer desaparecer.”

No tratante á xestión de memoria, a recollida de lixo tampouco ten maxia: consiste en detectar que bloques de memoria son accesibles a través de punteiros e referencias e liberar todos os bloques inaccesibles. A cuestión é como o facer.

Existen dúas estratexias fundamentais: a conta de referencias e o seguimento de referencias.

Na primeira estratexia, cada bloque leva a conta do número de referencias que apuntan a el. Cada vez que se asigna o enderezo do bloque a unha referencia, ese número aumenta; cando unha referencia que apuntaba a ese bloque recibe un enderezo distinto, o número diminúe. Se o número chega a cero, iso significa que xa non ten referencias apuntando a el e, polo tanto, é inaccesible, así que o sistema libera o bloque.

Esta estratexia é moi simple, aínda que ten algúns inconvenientes relacionados coa concorrencia. O máis grave, porén, é que non funciona ben con referencias circulares. Se un bloque “A” contén unha referencia que apunta a un bloque “B” e este bloque contén outra referencia ao bloque “A”, os dous bloques van figurar como accesibles mesmo se non hai ningunha outra referencia que apunte a eles, así que o sistema non os vai liberar.

Varios bloques de memoria con números que indican cantos punteiros levan a eles. Dous bloques teñen conta “1” malia seren inaccesibles.Os dous bloques do medio non son accesibles (non hai maneira de chegar a eles seguindo punteiros desde a rima ou as variables globais) pero teñen conta “1” porque cada un apunta ao outro; polo tanto, non se han liberar.

A estratexia de seguimento de referencias iníciase buscando referencias nas variables globais e na rima; despois o sistema percorre estas referencias para visitar os bloques de memoria aos que apuntan e atopar novas referencias para percorrer. Cando non quedan máis bloques por visitar, consérvanse todos os bloques visitados e libéranse os bloques que non se visitaron por seren inaccesibles.

Esta foi unha explicación conceptual, porque na realidade hai moitas maneiras de facelo. Unha delas chámase “marcar e varrer”, e nela cada bloque ten un bit que indica se se visitou o bloque. No principio do proceso todos os bits póñense a cero e, ao visitar cada bloque, ponse a un (“marcar”). No final libéranse os bloques que fiquen cun bit cero (“varrer”).

Varios bloques de memoria; os que son accesibles mediante os punteiros están marcados.Os bloques accesibles seguindo punteiros desde a rima ou as variables globais están marcados; os outros van quedar liberados cando se pase a vasoira, incluso se teñen punteiros entre eles.

Outra maneira de facelo é dividir a memoria en dúas zonas. No principio, todos os bloques están nunha zona e, cando se visita un deles, móvese á outra zona. Ao rematar, todo o que reste na zona orixinal abandónase e dáse por liberado. Esta estratexia semella máis laboriosa por culpa dos movementos de memoria e todo iso, pero ten a vantaxe de que non ten que liberar cada bloque individualmente e, ademais, ao mover os bloques á outra zona, a memoria libre queda desfragmentada.

Estes sistemas que veño de describir teñen un problema fundamental: o programa non pode facer nada mentres dura a recollida do lixo. Isto causa pausas que poden durar moito tempo: incluso segundos, se hai moitos bloques de memoria reservados.

Para solucionalo inventáronse moitas maneiras de facer a recollida do lixo de maneira “incremental”: no canto de recoller todo dunha vez cada moito tempo, facer pequenas recollidas frecuentes e que duran pouco tempo.

Unha delas consiste en non dividir os bloques en dous grupos (“conservar” e “eliminar”) senón en tres (“conservar”, “por mirar” e “eliminar”). O sistema colle un bloque “por mirar”, móveo a “conservar” e, se tiña referencias a bloques de “eliminar”, móveos a “por mirar”. Cando non quedan bloques “por mirar”, o sistema pode liberar todos os bloques restantes do grupo “eliminar”.

Con este sistema, o programa pode mover bloques dun grupo ao outro segundo reserva memoria e modifica referencias, deixando medio traballo xa feito, e pode mirar cantos bloques hai en cada grupo e argallar para facer unha recollida pequena de cando en vez.

Outra maneira de reducir o tamaño das recollidas é usando xeracións. Normalmente, os bloques de memoria duran moi pouco tempo (resérvanse e libéranse deseguido) ou moito tempo (resérvanse no principio do programa e quedan ata que o programa remata), así que algúns sistemas dividen a memoria en “xeracións” e fan a recollida do lixo só na xeración máis nova, dando por accesibles os bloques das xeracións máis vellas. Se aínda con iso non hai suficiente espazo na xeración máis nova, moven os bloques sobreviventes á seguinte xeración (facendo nela unha recollida do lixo de ser necesario), e así sucesivamente.

Como vedes, hai moitas maneiras de recoller o lixo nos vosos programas (e moitas máis das que non falei). Se tedes interese en que os vosos programas teñan bo rendemento, é importante que saibades como funciona o sistema de recollida do lixo da vosa linguaxe de programación para saber se vos convén máis ter moitos bloques pequenos ou poucos bloques grandes e para que poidades facer axustes no tamaño e número das distintas rexións ou xeracións.


Algúns din que hai unha terceira estratexia para a recollida do lixo: durante a compilación, facer unha análise do uso que se fai das variables e referencias para detectar o punto do programa no que se tornan inaccesibles e liberar a súa memoria aí; desta maneira non é necesario recoller o lixo mentres se executa o programa. Esta é a estratexia que usa a linguaxe de programación Rust.

Eu, particularmente, non conto isto como unha estratexia de recollida do lixo polo mesmo motivo que ninguén di que a calvicie é un peiteado. Pero igual son só eu. Vós que pensades?