Go 1.25 y 1.26: el compilador que te hace el trabajo sucio
Imagina que estás procesando tareas de un canal en Go, construyendo un slice dinámicamente con append. Primera iteración: allocación de backing store tamaño 1. Segunda iteración: backing store lleno, nueva allocación tamaño 2, el anterior se convierte en garbage. Tercera iteración: lo mismo pero tamaño 4. Y así sucesivamente, duplicando el tamaño cada vez que se llena.
Durante esta fase de "startup" gastas mucho tiempo en el allocator y produces bastante garbage, lo cual es desperdicio puro. Especialmente si tu slice nunca crece realmente mucho.
La optimización manual que funcionaba
Tradicionalmente, si este código era una parte crítica de tu aplicación, podrías estar tentado a inicializar el slice con un tamaño estimado:
tasks := make([]task, 0, 10) // probablemente máximo 10 tareasEsta es una optimización razonable. Si tu estimación era buena, reducías las allocaciones a exactamente 1. Pero si benchmarkearas este código con 10 elementos en el canal, descubrirías algo sorprendente: no redujiste las allocaciones a 1, las redujiste a 0.
La razón es que el compilador decidió allocar el backing store en el stack. Como sabe qué tamaño necesita (10 veces el tamaño de una task), puede allocar storage en el stack frame en lugar del heap.
El problema de los tamaños variables
Pero claro, hardcodear un guess de tamaño es rígido. ¿Qué tal si pasas un lengthGuess estimado?
func process3(c chan task, lengthGuess int) {
tasks := make([]task, 0, lengthGuess)
// ...
}Desafortunadamente, en Go 1.24 el tamaño no-constante del backing store significa que el compilador ya no puede allocar en el stack. Termina en el heap, convirtiendo tu código de 0-allocaciones en 1-allocación.
Go 1.25: allocación especulativa automática
Pero Go 1.25 cambia las reglas del juego. El compilador ahora hace automáticamente una allocación especulativa en el stack para tamaños variables razonables.
Foto de Daniil Komov en Unsplash