Perfilado de código Go: encontrando cuellos de botella en la práctica

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

Introducción: por qué el perfilado de Go es imprescindible para el desarrollador

\n

Go 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.

\n\n

1. Perfilado de CPU con pprof: buscando puntos críticos

\n

El perfilado de CPU muestra qué funciones consumen más tiempo de procesador. El paquete integrado runtime/pprof permite recopilar perfiles fácilmente.

\n\n

1.1. Agregando perfilado al código

\n

Consideremos un ejemplo de un servidor HTTP simple que realiza cálculos pesados:

\n
package 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
\n

Después de iniciar el servidor y realizar algunas solicitudes, tendrá un archivo cpu.prof. Analícelo con el comando:

\n
go tool pprof -http=:8081 cpu.prof
\n

Se 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.

\n\n

1.2. Interpretación de resultados

\n

En 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
\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).

\n\n

2. Perfilado de memoria: buscando fugas y asignaciones

\n

Las 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.

\n\n

2.1. Recopilación del perfil de heap

\n

Agreguemos al código la recopilación del perfil de memoria mediante la señal SIGUSR1:

\n
import (\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
\n

Después de enviar la señal kill -SIGUSR1 <PID> obtendrá el archivo mem.prof. Analícelo:

\n
go tool pprof -http=:8082 mem.prof
\n\n

2.2. Problemas típicos de memoria

\n
    \n
  • Gran cantidad de asignaciones pequeñas — por ejemplo, crear cadenas con + en lugar de strings.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
\n

Ejemplo de código no óptimo:

\n
func 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
\n

Optimiz

Blogs

Book Recommendations