Perfilado de código Go: encontrando cuellos de botella para principiantes
\n\nCuando su proyecto Go comienza a ralentizarse, consumir demasiada memoria o funcionar de manera inestable, surge una pregunta lógica: "¿Dónde está el cuello de botella?" Las conjeturas intuitivas rara vez ayudan: se necesitan datos precisos. El perfilado (profiling) es el proceso de recopilar métricas sobre el funcionamiento de un programa: cuánto tiempo toma cada función, cuánta memoria se asigna, con qué frecuencia se llama al recolector de basura. En este artículo, analizaremos cómo usar las herramientas integradas de Go (pprof y trace) para encontrar y solucionar rápidamente problemas de rendimiento.
\n\n¿Qué es el perfilado y para qué sirve?
\n\nEl perfilado es como una radiografía para su código. No solo ve "el programa funciona lento", sino cifras concretas: la función parseData() ocupa el 40% del tiempo, y loadConfig() asigna 2 GB de memoria. Para Go existen varios tipos de perfiles:
- \n
- Perfil de CPU — muestra qué funciones consumen tiempo de procesador. \n
- Perfil de memoria (heap) — muestra qué funciones asignan memoria y dónde viven los objetos. \n
- Perfil de Goroutines — muestra la cantidad de gorutinas activas y su estado. \n
- Perfil de bloqueo — muestra dónde se bloquean las gorutinas en mutexes o canales. \n
- Perfil de Mutex — muestra la contención por mutexes. \n
Para principiantes, lo más útil será el perfilado de CPU y memoria. Estos proporcionan el 80% de las respuestas a la pregunta "¿por qué el código se ralentiza?".
\n\nHerramienta pprof: el perfilador integrado de Go
\n\nGo viene con el paquete integrado net/http/pprof. Este agrega endpoints a su servidor HTTP desde los cuales se pueden tomar perfiles. Veamos un ejemplo básico.
Paso 1: Conectamos pprof a la aplicación
\n\npackage main\n\nimport (\n "fmt"\n "log"\n "net/http"\n _ "net/http/pprof" // conectamos el perfilador\n "time"\n)\n\nfunc main() {\n // Iniciamos el servidor en el puerto 8080\n go func() {\n log.Println(http.ListenAndServe("localhost:8080", nil))\n }()\n\n // Nuestra lógica de negocio "pesada"\n for i := 0; i < 100; i++ {\n go heavyTask(i)\n }\n\n // Esperamos a que termine\n time.Sleep(10 * time.Second)\n fmt.Println("Done")\n}\n\nfunc heavyTask(id int) {\n // Simulación de trabajo pesado\n for i := 0; i < 1000000; i++ {\n _ = i * i\n }\n fmt.Printf("Task %d finished\\n", id)\n}\n\n\nDespués de iniciar la aplicación, abra el navegador en http://localhost:8080/debug/pprof/. Verá una interfaz web con enlaces a los perfiles.
Paso 2: Tomamos un perfil de CPU
\n\nLa forma más sencilla es usar la línea de comandos. Ejecute:
\n\ngo tool pprof http://localhost:8080/debug/pprof/profile?seconds=30\n\nEste comando recopilará un perfil de CPU durante 30 segundos. Una vez finalizado, se abrirá la consola interactiva de pprof. Escriba top para ver las funciones más "calientes":
(pprof) top\nShowing nodes accounting for 1.50s, 100% of 1.50s total\n flat flat% sum% cum cum%\n 1.50s 100% 100% 1.50s 100% main.heavyTask\n 0 0% 100% 1.50s 100% runtime.main\n\n\nAquí flat es el tiempo gastado directamente dentro de la función, y cum es el tiempo incluyendo las llamadas a funciones hijas. En nuestro ejemplo, el 100% del tiempo se va en heavyTask.
Paso 3: Visualización a través de la interfaz web
\n\nPara un análisis más visual, use el modo gráfico:
\n\ngo tool pprof -http=:8081 http://localhost:8080/debug/pprof/profile?seconds=30\n\nSe abrirá un navegador con un gráfico de llamadas interactivo. Cuanto más grande y rojo sea el bloque, más recursos consume. Esta es una excelente manera de encontrar rápidamente funciones "pesadas".
\n\n