Introducción: por qué el perfilado de código Go es imprescindible
El desarrollo en Go (Golang) es conocido por su rendimiento y eficiencia. Sin embargo, incluso el código más rápido puede ralentizarse si no se tienen en cuenta las particularidades del manejo de memoria, las gorutinas o la entrada/salida. El perfilado es el proceso de recopilación de datos sobre la ejecución de un programa, que permite mirar «bajo el capó» y comprender qué funciones consumen más CPU, memoria o tiempo.
En este artículo, analizaremos las herramientas prácticas de perfilado en Go: pprof, trace y los benchmarks integrados. Aprenderá a encontrar cuellos de botella y optimizar el código con ejemplos reales.
1. Herramientas principales de perfilado en Golang
Go proporciona un potente arsenal para el perfilado directamente desde su instalación. Consideremos tres enfoques clave:
- pprof — perfilado de CPU, memoria, bloqueos y gorutinas.
- trace — trazado de eventos: creación de gorutinas, llamadas al sistema, GC.
- benchmark — pruebas de rendimiento integradas con
go test -bench.
Para empezar a trabajar con pprof, basta con importar net/http/pprof en su servidor HTTP o usar runtime/pprof para aplicaciones CLI.
1.1. Cómo ejecutar el perfilado de CPU
La forma más sencilla es agregar un endpoint a su servidor HTTP:
package main
import ( "log" "net/http" _ "net/http/pprof")
func main() { go func() { log.Println(http.ListenAndServe("localhost:6060", nil)) }() // su código principal select {}}Después de ejecutarlo, abra http://localhost:6060/debug/pprof/ — verá un panel. Para recopilar un perfil de CPU durante 30 segundos, ejecute:
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30Esto descargará los datos y abrirá la consola interactiva de pprof.
2. Encontrando cuellos de botella con pprof: CPU y memoria
Supongamos que tenemos una función que procesa un gran conjunto de datos. Sospechamos que está ralentizando el programa. Escribamos un ejemplo de prueba y perfilémoslo.
2.1. Ejemplo de código lento
package main
import ( "fmt" "time")
func slowFunction(data []int) int { sum := 0 for _, v := range data { time.Sleep(1 * time.Microsecond) // simulación de trabajo sum += v * v } return sum}
func main() { data := make([]int, 100000) for i := range data { data[i] = i } result := slowFunction(data) fmt.Println(result)}Ejecutemos el perfilado de CPU:
go run main.go &go tool pprof -http=:8080 http://localhost:6060/debug/pprof/profile?seconds=10En el navegador se abrirá una visualización: un grafo de llamadas (call graph). El grosor de las aristas muestra cuánto tiempo tomó cada función. Veremos inmediatamente que slowFunction y time.Sleep son los principales consumidores de CPU.
2.2. Análisis de memoria (heap profile)
Para encontrar fugas de memoria u objetos que ocupan demasiado espacio, use:
go tool pprof http://localhost:6060/debug/pprof/heapEn la consola interactiva puede ejecutar el comando top:
(pprof) top10Showing nodes accounting for 80MB, 100% of 80MB total flat flat% sum% cum cum% 40MB 50.00% 50.00% 40MB 50.00% main.slowFunction 20MB 25.00% 75.00% 20MB 25.00% main.createLargeSlice 20MB 25.00% 100.00% 20MB 25.00% runtime.makesliceEsto muestra qué funciones asignan más memoria. Si ve que main.slowFunction asigna 40MB, debería considerar una refactorización, por ejemplo, usar la reutilización de slices.