paint-brush
Go: la guía completa para perfilar su códigopor@josie
59,711 lecturas
59,711 lecturas

Go: la guía completa para perfilar su código

por Ali Josie8m2020/11/11
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow
ES

Demasiado Largo; Para Leer

Go es un lenguaje de programación que se usa a menudo para aplicaciones en las que el rendimiento importa. Debe tener información sobre el rendimiento de su código y los cuellos de botella para poder optimizarlo de manera eficiente. Go viene con soporte incorporado para la creación de perfiles. Dave Cheney ha desarrollado un paquete llamado "premium-prof" para hacer que la creación de perfiles sea muy fácil. Go: la guía completa para crear perfiles de su código. Obtenga los detalles sobre cómo obtener un perfil en su código y cómo descargar perfiles en vivo del paquete.

Companies Mentioned

Mention Thumbnail
Mention Thumbnail
featured image - Go: la guía completa para perfilar su código
Ali Josie HackerNoon profile picture

Go es un lenguaje de programación que se usa a menudo para aplicaciones en las que el rendimiento importa. Por supuesto, optimizar su código basado en suposiciones no es una buena práctica. Debe tener información sobre el rendimiento de su código y los cuellos de botella para poder optimizarlo de manera eficiente.

¿Qué es un perfilador?

Profiler es una herramienta de análisis de rendimiento dinámico que proporciona
Insights de ejecución en varias dimensiones que permiten resolver
problemas de rendimiento, localización de fugas de memoria, contención de subprocesos y más.

¡Go viene con soporte incorporado para la creación de perfiles!

¿Qué tipos de perfiles puedo obtener?

Go tiene varios perfiles integrados:

  • Goroutine: apila rastros de todos los Goroutines actuales
  • CPU: apilar rastros de CPU devueltos por el tiempo de ejecución
  • Montón: una muestra de las asignaciones de memoria de los objetos en vivo
  • Asignación: una muestra de todas las asignaciones de memoria pasadas
  • Subproceso: apilar rastros que llevaron a la creación de nuevos subprocesos del sistema operativo
  • Bloquear: apilar rastros que condujeron al bloqueo en las primitivas de sincronización
  • Mutex: apilar rastros de titulares de mutex en disputa

¿Cómo obtener un perfil?

Hay varias formas de crear un perfil.

1. Usando "ir a prueba" para generar perfil

Compatibilidad con la creación de perfiles integrada en el paquete de pruebas estándar. Como ejemplo, el siguiente comando ejecuta todos los puntos de referencia y escribe el perfil de CPU y memoria en

 cpu.prof
y
 mem.prof
:

 go test -cpuprofile cpu.prof -memprofile mem.prof -bench .

2. Descarga el perfil en vivo usando HTTP

Si necesita tener un perfil activo en un servicio de larga duración, empaquete

 net/http/pprof
es su solución definitiva. Solo importe el paquete en su código y estará listo para comenzar:

 import _ "net/http/pprof"

La inicialización de este paquete es la razón por la que necesita importarlo únicamente.

 func init () { http.HandleFunc( "/debug/pprof/" , Index) http.HandleFunc( "/debug/pprof/cmdline" , Cmdline) http.HandleFunc( "/debug/pprof/profile" , Profile) http.HandleFunc( "/debug/pprof/symbol" , Symbol) http.HandleFunc( "/debug/pprof/trace" , Trace) }

Como puede ver, la inicialización del paquete registra los controladores para la URL dada en

 DefaultServeMux
. Estos controladores escriben el perfil en un
 http.ResponseWriter
que puedes descargar. Ahora lo único que debe hacer es ejecutar un servidor HTTP con un
 nil
manipulador:

 func init () { go func () { http.ListenAndServe( ":1234" , nil ) }() }

Tenga en cuenta que si ya ejecuta su servidor http con

 DefaultServeMux
, no necesita ejecutar otro servidor y los perfiles están disponibles en su servidor.

Ahora, puede descargar datos de perfil en vivo desde el

 /debug/pprof/
URL

3. Perfilado en código

Usando

 runtime/pprof
, También puede perfilar directamente dentro del código. Por ejemplo, puede iniciar un perfil de CPU usando
 pprof.StartCPUProfile(io.Writer)
y luego detenerlo
 pprof.StopCPUProfile()
. Ahora tiene el perfil de la CPU escrito en el dado
 io.Writer
.

Pero espera, hay una opción más fácil. Dave Cheney ha desarrollado un paquete llamado

 profile
que puede hacer que la creación de perfiles sea muy fácil. Primero debe obtener el paquete usando el siguiente comando (o usando los módulos Go):

 go get github.com/pkg/profile

Entonces puedes usarlo así:

 func main () { defer profile.Start(profile.ProfilePath( "." )).Stop() // do something }

El código anterior escribirá el perfil de la CPU en

 cpu.pprof
en el directorio de trabajo. El valor predeterminado se ha establecido en la creación de perfiles de CPU, pero puede pasar opciones a
 profile.Start()
para poder obtener otros perfiles también.

 // CPUProfile enables cpu profiling. Note: Default is CPU defer profile.Start(profile.CPUProfile).Stop() // GoroutineProfile enables goroutine profiling. // It returns all Goroutines alive when defer occurs. defer profile.Start(profile.GoroutineProfile).Stop() // BlockProfile enables block (contention) profiling. defer profile.Start(profile.BlockProfile).Stop() // ThreadcreationProfile enables thread creation profiling. defer profile.Start(profile.ThreadcreationProfile).Stop() // MemProfileHeap changes which type of memory profiling to // profile the heap. defer profile.Start(profile.MemProfileHeap).Stop() // MemProfileAllocs changes which type of memory to profile // allocations. defer profile.Start(profile.MemProfileAllocs).Stop() // MutexProfile enables mutex profiling. defer profile.Start(profile.MutexProfile).Stop()

¿Cómo usar un perfil?

Así que hemos recopilado los datos del perfil, ¿y ahora qué?

pprof es una herramienta para la visualización y el análisis de datos de perfiles. Lee una colección de muestras de perfiles en

 profile.proto
formato y genera informes para visualizar y ayudar a analizar los datos. Puede generar informes de texto y gráficos.

Todas las formas mencionadas anteriormente están utilizando

 runtime/pprof
bajo el capó y este paquete escribe datos de perfilado en tiempo de ejecución en el formato esperado por la herramienta de visualización pprof.

Escriba un código simple para el perfil

Para una mejor comprensión, he escrito el siguiente código para perfilar. Es un servidor HTTP simple que maneja solicitudes en

 /log
URL Cada solicitud llega, el
 logHandler
La función declarará un
 chan int
y cree un Goroutine hijo en la línea 16. El hijo decodificará el registro JSON dado dentro del
 Request.Body
y si tuvo éxito, realiza una escritura que consume mucho tiempo (~ 400 milisegundos) y luego envía
 http.StatusOK
a
 ch
. Mientras tanto el
 logHandler
bloques de funciones en la declaración de selección (línea 27) hasta que el niño envíe el resultado a
 ch
(línea 24) o cuando ocurre un tiempo de espera (línea 30).

 import ( "encoding/json" "math/rand" "net/http" _ "net/http/pprof" "time" ) func main () { http.HandleFunc( "/log" , logHandler) http.ListenAndServe( ":8080" , nil ) } func logHandler (w http.ResponseWriter, r *http.Request) { ch := make ( chan int ) go func () { obj := make ( map [ string ] float64 ) if err := json.NewDecoder(r.Body).Decode(&obj); err != nil { ch <- http.StatusBadRequest return } // simulation of a time consuming process like writing logs into db time.Sleep(time.Duration(rand.Intn( 400 )) * time.Millisecond) ch <- http.StatusOK }() select { case status := <-ch: w.WriteHeader(status) case <-time.After( 200 * time.Millisecond): w.WriteHeader(http.StatusRequestTimeout) } }

Compilé y ejecuté el código y le envié algunas cargas usando hey . Aquí está la salida:

 Summary: Total: 0.8292 secs Slowest: 0.2248 secs Fastest: 0.0039 secs Average: 0.1574 secs Requests/sec: 241.1882Latency distribution: 10% in 0.0409 secs 25% in 0.1143 secs 50% in 0.2007 secs 75% in 0.2013 secs 90% in 0.2093 secs 95% in 0.2187 secs 99% in 0.2245 secsStatus code distribution: [200] 87 responses [408] 113 responses

Conceptos básicos de pprof

Después de escribir, ejecutar y enviar la carga a nuestro servicio, es hora de ejecutar la herramienta pprof en el perfil del servicio para ver qué funciones consumieron más durante la carga.

Instalar la herramienta pprof

Puedes usar

 go tool pprof
comando o instale pprof para usarlo directamente.

 go get -u github.com/google/pprof

Ejecutar pprof en modo interactivo

Ahora ejecutemos pprof en objetos asignados usando el siguiente comando:

 pprof -alloc_objects http://:8080/debug/pprof/allocs

Esto abrirá un shell interactivo simple que toma comandos pprof para generar informes.

Ahora ingrese el comando

 top
, la salida serán los principales consumidores de memoria.

Nodos descartados

Como puede ver en el resultado, se descartaron 11 nodos (línea 3). Un nodo es una entrada de objeto y eliminar un nodo puede ayudar a reducir el ruido, pero a veces puede ocultar la causa raíz del problema.

Agregando el

 -nodefraction=0
flag o asignándolo directamente dentro del shell atractivo incluirá todos los datos del perfil en el resultado.

Diferencia entre plana y acumulativa

En el resultado anterior, puede ver dos columnas,

 flat
y
 cum
. Plano significa solo por esta función , mientras que cum (acumulativo) significa por esta función y funciones llamadas en la pila. Para una mejor comprensión, supongamos la siguiente función, el tiempo plano de la función
 A
es 4s y el semen es 11s.

 func A () { B() // takes 1s DO STH DIRECTLY // takes 4s C() // takes 6s }

Diferencia entre asignaciones y perfil de montón

Una nota importante a tener en cuenta es que la diferencia entre las asignaciones y el perfil del montón es la forma en que la herramienta pprof los lee en el momento de inicio. El perfil de asignaciones iniciará pprof en un modo que muestra la cantidad total de objetos asignados desde que comenzó el programa, que también incluye los que se recolectaron como elementos no utilizados, pero el perfil de almacenamiento dinámico se iniciará en un modo que muestra la cantidad de objetos asignados activos que no incluye los bytes recolectados como elementos no utilizados. .

Eso significa que en la herramienta pprof puede cambiar el modo independientemente del
tipo de perfil (montón o asignaciones). Se puede hacer usando los siguientes valores. Puede configurarlos como una bandera pprof o asignarlos a

 sample_index
dentro del caparazón interactivo pprof:

  •  inuse_space
    : cantidad de memoria asignada y aún no liberada (montón)
  •  inuse_objects
    : cantidad de objetos asignados y aún no liberados (montón)
  •  alloc_space
    : cantidad total de memoria asignada (independientemente de la liberada)
  •  alloc_objects
    : cantidad total de objetos asignados (independientemente de liberados)

Obtenga un gráfico SVG del perfil

Puedes generar un gráfico de tu perfil en formato SVG y abrirlo
con un navegador web. El siguiente comando solicitará una CPU 5s
perfil e iniciará un navegador con un archivo SVG.

 pprof -web http://:8080/debug/pprof/profile?seconds=5

Ejecute pprof a través de una interfaz web

Una de las características útiles de pprof es la interfaz web. Si el

 -http
se especifica el indicador, pprof inicia un servidor web en el host:por especificado que proporciona una interfaz interactiva basada en web para pprof.

 pprof -http :8080 http://:8080/debug/pprof/goroutine

El comando anterior debería abrir automáticamente su navegador web en la página de la derecha; si no, puede visitar manualmente el puerto especificado en su navegador web.

Supervisión de rutinas Gor desde la línea de comandos

Como puede ver en el perfil de Goroutine, 115 Goroutines están activos, lo que no es normal para un servicio inactivo. Debería ser una fuga de Goroutine. Entonces, verifiquemos el estado de Goroutines usando la herramienta grmon . grmon es un monitoreo de línea de comando para Goroutines. Instalémoslo y ejecútelo en el host que sirve los datos del perfil:

 go get -u github.com/bcicen/grmon grmon -host :8080

La salida es:

parece que

 logHandler
bloques de funciones en un
 chan send
acción. Después de revisar el código encontré el error. Ha ocurrido debido a un envío a un canal no garantizado. Lo he arreglado a su vez el
 ch
a un canal almacenado en búfer (línea 15). Fue el mismo error que mencioné en esta publicación .

¿Cómo funciona un perfilador?

Aunque los seguimientos de pila generados por los generadores de perfiles a menudo son inteligibles, a veces es necesario comprender cómo funcionan los generadores de perfiles para obtener aún más detalles de los perfiles generados.

Analizador de CPU

El generador de perfiles Go CPU utiliza un

 SIGPROF
Señal para registrar estadísticas de ejecución de código. Una vez que la señal se registró, entregará cada intervalo de tiempo especificado. Este temporizador, a diferencia de los temporizadores típicos, se incrementará cuando la CPU esté ejecutando el proceso. La señal interrumpirá la ejecución del código y permitirá ver qué código se interrumpió.

Cuando el

 pprof.StartCPUProfile
se llama la función, la
 SIGPROF
el controlador de señal se registrará para llamar cada intervalo de 10 milisegundos de forma predeterminada (100 Hz). En Unix, utiliza una llamada al sistema setitimer(2) para configurar el temporizador de la señal.

En cada señal de invocación, el controlador rastreará la ejecución desviándola del valor de PC actual. Esto generará un seguimiento de la pila e incrementará su recuento de visitas. Todo el proceso dará como resultado una lista de seguimientos de pila agrupados por la cantidad de veces que se ve cada seguimiento de pila.

Después de detener el generador de perfiles, los seguimientos de la pila se convertirán en marcos de pila compatibles con pprof con nombres de archivos de origen, números de línea y nombres de funciones.

Perfilador de memoria

El generador de perfiles de memoria muestra las asignaciones de montones . Mostrará asignaciones de llamadas de funciones. Registrar todas las asignaciones y desenredar el seguimiento de la pila sería costoso, por lo que se utiliza una técnica de muestreo.

El proceso de muestreo se basa en un generador de números pseudoaleatorios basado en una distribución exponencial para muestrear solo una fracción de las asignaciones. Los números generados definen la distancia entre las muestras en términos del tamaño de la memoria asignada. Esto significa que solo se muestrearán las asignaciones que crucen el siguiente punto de muestreo aleatorio.

La frecuencia de muestreo define la media de la distribución exponencial. El valor predeterminado es 512 KB, que está especificado por el

 runtime.MemProfileRate
. Eso significa que para ajustar el tamaño de la fracción a muestrear, debe cambiar
 runtime.MemProfileRate
valor. Al establecer el valor en 1, se incluirán todos los bloques asignados en el perfil. Obviamente, esto ralentizará su aplicación.

Los seguimientos de pila con la información de asignaciones muestreadas correspondientes están disponibles en cualquier momento, ya que el generador de perfiles de asignación de memoria siempre está activo. Configuración de la

 runtime.MemProfileRate
a 0, desactivará por completo el muestreo de memoria.

Una vez que los seguimientos de la pila estén listos, se convertirán en marcos de pila compatibles con pprof.

También publicado aquí .