La recolección de basura (GC) en Go es un mecanismo automático de gestión de memoria que apareció por primera vez en las primeras versiones del lenguaje. Históricamente, el GC en Go fue una de las fuentes de crítica debido a su impacto en el rendimiento. Sin embargo, con el desarrollo del lenguaje, especialmente después de la versión Go 1.5, se ha mejorado significativamente: ahora se utiliza un recolector de basura concurrente triple (concurrent, tricolor, mark-and-sweep) con pausas mínimas (low-pause GC).
El problema surge cuando las aplicaciones crean una gran cantidad de objetos temporales, o cuando no eliminan referencias a estructuras no utilizadas: esto aumenta la carga en el GC y puede llevar a pausas prolongadas. Se debe prestar especial atención a los tipos de objetos, referencias cíclicas y cadenas largas de referencias que están fuera de la pila.
La solución es vigilar la asignación de memoria, utilizar la perfilación y ajustar el GC a través de la variable de entorno GOGC, minimizando la cantidad de asignaciones en bucles internos y en secciones críticas. Es importante recordar que la recolección de basura en Go solo funciona para el montón (heap): todo lo que se asigna en la pila se eliminará automáticamente al salir del alcance, mientras que los objetos que "se van" al montón son controlados por el GC.
Ejemplo de código:
// Perfilación del uso de memoria y optimización del GC import ( "runtime" "fmt" ) func main() { var memStats runtime.MemStats runtime.ReadMemStats(&memStats) fmt.Printf("Antes de la asignación: %d bytes ", memStats.Alloc) s := make([]int, 1_000_000) for i := range s { s[i] = i } runtime.GC() // limpieza manual runtime.ReadMemStats(&memStats) fmt.Printf("Después del GC: %d bytes ", memStats.Alloc) }
Características clave:
GOGC (por ejemplo, GOGC=100 — estándar; disminuir acelera el GC, pero aumenta el consumo de CPU).¿Cómo saber qué objeto "se va" al montón y cuál permanece en la pila?
Respuesta: Para esto se utiliza el análisis de escape, que se puede analizar utilizando la opción del compilador go build -gcflags="-m". Los objetos que se devuelven desde una función o que se utilizan en cierres, a menudo se van al montón.
Ejemplo de código:
func escape() *int { v := 42 return &v // v estará en el montón }
¿Afecta el GC a todas las variables, incluidas las que están en la pila?
No, el GC solo trabaja con el montón (objetos asignados en el heap). Todo lo que se asigna en la pila se limpia automáticamente al finalizar la función.
¿Puede la llamada manual a runtime.GC() mejorar significativamente el rendimiento?
Por el contrario, la llamada manual a menudo empeora el rendimiento, aumentando el consumo de CPU. Solo se debe usar para pruebas o casos que se están depurando.
Caso negativo
Un desarrollador escribe un servicio que crea nuevos grandes slices en cada solicitud, sin considerar su ciclo de vida. Esto rápidamente lleva a un aumento de la carga en el GC, pausas drásticas y una caída en el rendimiento.
Ventajas:
Desventajas:
Caso positivo
Un desarrollador optimiza el servicio, perfila las asignaciones, reutiliza buffers a través de sync.Pool, reduce la frecuencia de llamadas al GC y minimiza la asignación de memoria en los puntos críticos.
Ventajas:
Desventajas: