Pila o montón en Go: cómo la asignación de struct afecta al GC

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: ¿Por qué «simplemente struct» no es tan simple?

\n\n

¿Alguna vez has escrito en Go y, mirando tu programa, te has preguntado: «¿Dónde vive ahora mi estructura? ¿En la rápida y acogedora pila o en los vastos espacios del montón bajo la supervisión del recolector de basura?». Esta pregunta atormenta no solo a los principiantes. En Stack Overflow, el tema «Stack vs heap allocation of structs in Go, and how they relate to garbage collection» ha acumulado miles de visitas. Y no es casualidad.

\n\n

La aparente simplicidad de Go oculta un poderoso mecanismo: el escape analysis. Es un detective del compilador que decide el destino de cada objeto. De su decisión depende el rendimiento de tu aplicación y la carga sobre el GC (Garbage Collector). En este tutorial paso a paso, nos pondremos el sombrero de detective y analizaremos:

\n
    \n
  • ¿Por qué una estructura vive en la pila y otra en el montón?
  • \n
  • Cómo los punteros y los cierres rompen la idílica pila.
  • \n
  • Cómo este conocimiento te ayudará a escribir código rápido y eficiente en Go.
  • \n
\n\n

Pila vs Montón: Breve introducción

\n

Antes de sumergirnos en el código, refresquemos la teoría. No te preocupes, será rápido y directo.

\n\n

Pila (Stack)

\n
    \n
  • Velocidad: Relámpago. Asignar memoria es simplemente mover el puntero hacia abajo.
  • \n
  • Ciclo de vida: Estrictamente ligado a la llamada de función. Vive mientras la función está activa.
  • \n
  • Gestión: Automática. Se «quema» al salir de la función.
  • \n
  • Tamaño: Pequeño y fijo por gorutina (normalmente 2-4 KB).
  • \n
\n\n

Montón (Heap)

\n
    \n
  • Velocidad: Más lento. Requiere buscar un bloque de memoria libre.
  • \n
  • Ciclo de vida: Mientras haya referencias al objeto desde el programa. Puede sobrevivir a la función que lo creó.
  • \n
  • Gestión: Recolector de basura (GC). Escanea el montón, encuentra objetos «muertos» y libera memoria. Esto consume recursos de CPU.
  • \n
  • Tamaño: Grande (limitado solo por la memoria del SO).
  • \n
\n\n

La regla de oro de Go: «Mantén los datos en la pila el mayor tiempo posible». Esto reduce la carga sobre el GC y acelera el programa.

\n\n

Escape Analysis: El detective principal de Go

\n

En Go, no escribes new o malloc para indicar explícitamente dónde colocar un objeto. El compilador decide esto por ti. Este proceso se llama escape analysis (análisis de escape).

\n

Observa tu código y se hace una pregunta: «¿Puede una referencia a este objeto salir de la función actual?».

\n
    \n
  • Si NO (el objeto no escapa) → El objeto se coloca en la pila.
  • \n
  • Si SÍ (el objeto escapa) → El objeto se coloca en el montón.
  • \n
\n\n

Veamos esto en acción.

\n\n

Ejemplo 1: Estructura segura en la pila

\n
package main\n\nimport "fmt"\n\ntype Point struct {\n    X, Y int\n}\n\n// createPoint crea un Point y lo devuelve por valor.\nfunc createPoint(x, y int) Point {\n    p := Point{X: x, Y: y}\n    return p // p se copia al devolverlo\n}\n\nfunc main() {\n    p1 := createPoint(10, 20)\n    fmt.Println(p1)\n}\n
\n

En este ejemplo, p es una variable local. Se crea dentro de createPoint y se devuelve por valor (se copia). La estructura p en sí no puede ser utilizada fuera de la función (su copia se usa en main). El escape analysis lo ve y felizmente coloca p en la pila. El GC descansa.

\n\n

Ejemplo 2: Escape a través de un puntero

\n

Ahora cambiemos solo una línea: devolvamos un puntero.

\n
package main\n\nimport "fmt"\n\ntype Point struct {\n    X, Y int\n}\n\n// createPointer crea un Point y devuelve un puntero a él.\nfunc createPointer(x, y int) *Point {\n    p := P                

Blogs

Book Recommendations