Introducción: por qué el perfilado de Go es imprescindible para el desarrollador
\nGo es famoso por su rendimiento, pero incluso el código más rápido puede ralentizarse debido a algoritmos ineficientes, fugas de memoria o un manejo incorrecto de las gorutinas. El perfilado es la única forma de saber exactamente dónde gasta recursos su aplicación. En este artículo, analizaremos métodos prácticos para perfilar código Go utilizando herramientas integradas: pprof, trace y benchstat. Aprenderá a encontrar y eliminar cuellos de botella.
1. Perfilado de CPU con pprof: buscando puntos críticos
\nEl perfilado de CPU muestra qué funciones consumen más tiempo de procesador. El paquete integrado runtime/pprof permite recopilar perfiles fácilmente.
1.1. Agregando perfilado al código
\nConsideremos un ejemplo de un servidor HTTP simple que realiza cálculos pesados:
\npackage main\n\nimport (\n "fmt"\n "log"\n "net/http"\n _ "net/http/pprof"\n "os"\n "runtime/pprof"\n)\n\nfunc heavyTask() {\n sum := 0\n for i := 0; i < 1_000_000; i++ {\n sum += i * i\n }\n _ = sum\n}\n\nfunc handler(w http.ResponseWriter, r *http.Request) {\n heavyTask()\n fmt.Fprintf(w, "Done")\n}\n\nfunc main() {\n f, _ := os.Create("cpu.prof")\n pprof.StartCPUProfile(f)\n defer pprof.StopCPUProfile()\n\n http.HandleFunc("/", handler)\n log.Fatal(http.ListenAndServe(":8080", nil))\n}\n\nDespués de iniciar el servidor y realizar algunas solicitudes, tendrá un archivo cpu.prof. Analícelo con el comando:
go tool pprof -http=:8081 cpu.prof\nSe abrirá un gráfico interactivo en el navegador. Preste atención a los nodos rojos: indican las funciones más "calientes". En nuestro caso, es heavyTask.
1.2. Interpretación de resultados
\nEn la interfaz web de pprof verá:
\n- \n
- Graph — gráfico de llamadas con tamaños de nodo proporcionales al tiempo de ejecución. \n
- Flame Graph — gráfico de "llamas", donde las barras largas son las secciones más lentas. \n
- Top — tabla de funciones ordenadas por tiempo de CPU. \n
Si ve que heavyTask ocupa el 90% del tiempo, significa que debe optimizarla (por ejemplo, reemplazar el bucle con la fórmula de suma de cuadrados).
2. Perfilado de memoria: buscando fugas y asignaciones
\nLas fugas de memoria en Go son raras, pero las asignaciones excesivas pueden ralentizar mucho la aplicación. Usamos runtime/pprof para el perfil de heap.
2.1. Recopilación del perfil de heap
\nAgreguemos al código la recopilación del perfil de memoria mediante la señal SIGUSR1:
\nimport (\n "os"\n "os/signal"\n "runtime/pprof"\n "syscall"\n)\n\nfunc main() {\n // ... su código ...\n sigs := make(chan os.Signal, 1)\n signal.Notify(sigs, syscall.SIGUSR1)\n go func() {\n <-sigs\n f, _ := os.Create("mem.prof")\n pprof.WriteHeapProfile(f)\n f.Close()\n os.Exit(0)\n }()\n // ...\n}\n\nDespués de enviar la señal kill -SIGUSR1 <PID> obtendrá el archivo mem.prof. Analícelo:
go tool pprof -http=:8082 mem.prof\n\n2.2. Problemas típicos de memoria
\n- \n
- Gran cantidad de asignaciones pequeñas — por ejemplo, crear cadenas con
+en lugar destrings.Builder. \n - Mantener referencias a objetos grandes — después de usarlos, hay que poner los punteros a nil. \n
- Gorutinas que no finalizan — cada gorutina consume al menos 4 KB de pila. \n
Ejemplo de código no óptimo:
\nfunc badConcat(n int) string {\n s := ""\n for i := 0; i < n; i++ {\n s += "x" // cada iteración crea una nueva cadena\n }\n return s\n}\n\nOptimiz