¿Tu código Go funciona lento? Es hora de mirar bajo el capó
\n\nEscribiste un servicio en Go. Funciona. Pero cuando la carga alcanza las 10 000 RPS, el servidor comienza a «asfixiarse». ¿Te suena familiar? Go es famoso por su rendimiento «de fábrica», pero incluso en este elegante lenguaje hay trampas. Fugas de gorutinas, gestión ineficiente de la memoria, asignaciones innecesarias: todo esto mata la velocidad.
\n\nEn este artículo, analizaremos cómo el perfilado de Go ayuda a encontrar cuellos de botella y mostraremos técnicas concretas de optimización de aplicaciones Go. Aprenderás cómo usar las herramientas integradas pprof y go test -bench para convertir tu aplicación en un cohete. ¡Vamos allá!
\n\nKit de herramientas del perfilador: pprof y trace
\n\nGo proporciona dos potentes herramientas para analizar el rendimiento: pprof (perfilado de CPU, memoria, bloqueos) y go tool trace (análisis de concurrencia y eventos del GC).
Integrando pprof en un servidor HTTP
\n\nLa forma más sencilla de empezar a perfilar es añadir net/http/pprof a tu servidor. Esto solo requiere una línea de importación:
import (\n _ "net/http/pprof"\n "net/http"\n)\n\nfunc main() {\n http.ListenAndServe(":8080", nil)\n}\n\nAhora los perfiles están disponibles en la dirección /debug/pprof/. Para tomar un perfil de CPU durante 30 segundos, ejecuta:
go tool pprof http://localhost:8080/debug/pprof/profile?seconds=30\n\nDespués de recopilar los datos, se iniciará una consola interactiva. El comando top15 mostrará las funciones más activas:
(pprof) top15\nShowing nodes accounting for 3.50s, 87.50% of 4.00s total\n flat flat% sum% cum cum%\n 1.20s 30.00% 30.00% 1.50s 37.50% main.heavyComputation\n\n¿Ves main.heavyComputation? Este es nuestro candidato para optimización. Para visualizarlo, usa web (si Graphviz está instalado) y obtendrás un gráfico de llamadas.
Perfilado de memoria: buscando fugas
\n\nLas fugas de memoria en Go suelen ser fugas de gorutinas o referencias olvidadas a objetos grandes. Tomemos un perfil del heap:
\n\ngo tool pprof http://localhost:8080/debug/pprof/heap\n\nEl comando alloc_objects mostrará qué funciones crean más objetos:
(pprof) alloc_objects\n 2.50GB 100% 100% 2.50GB 100% main.createLargeSlice\n\n¡Así encontramos una función que asigna 2.5 GB de memoria! Quizás está creando un slice enorme que se puede reutilizar con sync.Pool.
Escribiendo benchmarks: tu primer paso hacia la optimización
\n\nAntes de optimizar, hay que medir. Los benchmarks de Go son la herramienta estándar para medir el rendimiento de funciones individuales.
\n\nEjemplo: comparando concatenación de cadenas
\n\nSupongamos que concatenamos cadenas en un bucle. Una mala opción:
\n\nfunc ConcatSlow(parts []string) string {\n result := ""\n for _, p := range parts {\n result += p\n }\n return result\n}\n\nUna buena opción es usar strings.Builder:
func ConcatFast(parts []string) string {\n var b strings.Builder\n for _, p := range parts {\n b.WriteString(p)\n }\n return b.String()\n}\n\nEscribamos un benchmark (archivo concat_test.go):
func BenchmarkConcatSlow(b *testing.B) {\n parts := []string{"Hello", " ", "World", "!", " ", "From", " ", "Go"}\n for i := 0; i < b.N; i++ {\n ConcatSlow(parts)\n }\n}\n\nfunc BenchmarkConcatFast(b *testing.B) {\n parts := []string{"Hello", " ", "World", "!", " ", "From", " ", "Go"}\n for i := 0; i < b.N; i++ {\n ConcatFast(parts)\n }\n}\n\nEjecutamos:
\n\ngo test -bench=. -benchmem\n\nResultado:
\n\nBenchmarkConc