Rendimiento de Go: cómo perfilar y acelerar el código

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

¿Tu código Go funciona lento? Es hora de mirar bajo el capó

\n\n

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

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

Kit de herramientas del perfilador: pprof y trace

\n\n

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

\n\n

Integrando pprof en un servidor HTTP

\n\n

La 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:

\n\n
import (\n    _ "net/http/pprof"\n    "net/http"\n)\n\nfunc main() {\n    http.ListenAndServe(":8080", nil)\n}
\n\n

Ahora los perfiles están disponibles en la dirección /debug/pprof/. Para tomar un perfil de CPU durante 30 segundos, ejecuta:

\n\n
go tool pprof http://localhost:8080/debug/pprof/profile?seconds=30
\n\n

Después de recopilar los datos, se iniciará una consola interactiva. El comando top15 mostrará las funciones más activas:

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

\n\n

Perfilado de memoria: buscando fugas

\n\n

Las fugas de memoria en Go suelen ser fugas de gorutinas o referencias olvidadas a objetos grandes. Tomemos un perfil del heap:

\n\n
go tool pprof http://localhost:8080/debug/pprof/heap
\n\n

El comando alloc_objects mostrará qué funciones crean más objetos:

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

\n\n

Escribiendo benchmarks: tu primer paso hacia la optimización

\n\n

Antes de optimizar, hay que medir. Los benchmarks de Go son la herramienta estándar para medir el rendimiento de funciones individuales.

\n\n

Ejemplo: comparando concatenación de cadenas

\n\n

Supongamos que concatenamos cadenas en un bucle. Una mala opción:

\n\n
func ConcatSlow(parts []string) string {\n    result := ""\n    for _, p := range parts {\n        result += p\n    }\n    return result\n}
\n\n

Una buena opción es usar strings.Builder:

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

Escribamos un benchmark (archivo concat_test.go):

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

Ejecutamos:

\n\n
go test -bench=. -benchmem
\n\n

Resultado:

\n\n
BenchmarkConc                

Blogs

Book Recommendations