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: rastros de todos los Goroutines actuales Goroutine: apila apilar rastros de CPU devueltos por el tiempo de ejecución CPU: una muestra de las asignaciones de memoria de los objetos en vivo Montón: una muestra de todas las asignaciones de memoria pasadas Asignación: apilar rastros que llevaron a la creación de nuevos subprocesos del sistema operativo Subproceso: apilar rastros que condujeron al bloqueo en las primitivas de sincronización Bloquear: apilar rastros de titulares de mutex en disputa Mutex: ¿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 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 y : integrada en cpu.prof mem.prof go -cpuprofile cpu.prof -memprofile mem.prof -bench . test 2. Descarga el perfil en vivo usando HTTP Si necesita tener un perfil activo en un servicio de larga duración, empaquete es su solución definitiva. Solo importe el paquete en su código y estará listo para comenzar: net/http/pprof _ import "net/http/pprof" La inicialización de este paquete es la razón por la que necesita importarlo únicamente. { http.HandleFunc( , Index) http.HandleFunc( , Cmdline) http.HandleFunc( , Profile) http.HandleFunc( , Symbol) http.HandleFunc( , Trace) } func init () "/debug/pprof/" "/debug/pprof/cmdline" "/debug/pprof/profile" "/debug/pprof/symbol" "/debug/pprof/trace" Como puede ver, la inicialización del paquete registra los controladores para la URL dada en . Estos controladores escriben el perfil en un que puedes descargar. Ahora lo único que debe hacer es ejecutar un servidor HTTP con un manipulador: DefaultServeMux http.ResponseWriter nil { { http.ListenAndServe( , ) }() } func init () go func () ":1234" nil Tenga en cuenta que si ya ejecuta su servidor http con , no necesita ejecutar otro servidor y los perfiles están disponibles en su servidor. DefaultServeMux Ahora, puede descargar datos de perfil en vivo desde el URL /debug/pprof/ 3. Perfilado en código Usando , También puede perfilar directamente dentro del código. Por ejemplo, puede iniciar un perfil de CPU usando y luego detenerlo . Ahora tiene el perfil de la CPU escrito en el dado . runtime/pprof pprof.StartCPUProfile(io.Writer) pprof.StopCPUProfile() io.Writer Pero espera, hay una opción más fácil. ha desarrollado un paquete llamado 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): Dave Cheney profile go get github.com/pkg/profile Entonces puedes usarlo así: { profile.Start(profile.ProfilePath( )).Stop() } func main () defer "." // do something El código anterior escribirá el perfil de la CPU en en el directorio de trabajo. El valor predeterminado se ha establecido en la creación de perfiles de CPU, pero puede pasar opciones a para poder obtener otros perfiles también. cpu.pprof profile.Start() profile.Start(profile.CPUProfile).Stop() profile.Start(profile.GoroutineProfile).Stop() profile.Start(profile.BlockProfile).Stop() profile.Start(profile.ThreadcreationProfile).Stop() profile.Start(profile.MemProfileHeap).Stop() profile.Start(profile.MemProfileAllocs).Stop() profile.Start(profile.MutexProfile).Stop() // CPUProfile enables cpu profiling. Note: Default is CPU defer // GoroutineProfile enables goroutine profiling. // It returns all Goroutines alive when defer occurs. defer // BlockProfile enables block (contention) profiling. defer // ThreadcreationProfile enables thread creation profiling. defer // MemProfileHeap changes which type of memory profiling to // profile the heap. defer // MemProfileAllocs changes which type of memory to profile // allocations. defer // MutexProfile enables mutex profiling. defer ¿Cómo usar un perfil? Así que hemos recopilado los datos del perfil, ¿y ahora qué? es una herramienta para la visualización y el análisis de datos de perfiles. Lee una colección de muestras de perfiles en formato y genera informes para visualizar y ayudar a analizar los datos. Puede generar informes de texto y gráficos. pprof profile.proto Todas las formas mencionadas anteriormente están utilizando 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. runtime/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 URL Cada solicitud llega, el La función declarará un y cree un Goroutine hijo en la línea 16. El hijo decodificará el registro JSON dado dentro del y si tuvo éxito, realiza una escritura que consume mucho tiempo (~ 400 milisegundos) y luego envía a . Mientras tanto el bloques de funciones en la declaración de selección (línea 27) hasta que el niño envíe el resultado a (línea 24) o cuando ocurre un tiempo de espera (línea 30). /log logHandler chan int Request.Body http.StatusOK ch logHandler ch ( _ ) { http.HandleFunc( , logHandler) http.ListenAndServe( , ) } { ch := ( ) { obj := ( [ ] ) err := json.NewDecoder(r.Body).Decode(&obj); err != { ch <- http.StatusBadRequest } time.Sleep(time.Duration(rand.Intn( )) * time.Millisecond) ch <- http.StatusOK }() { status := <-ch: w.WriteHeader(status) <-time.After( * time.Millisecond): w.WriteHeader(http.StatusRequestTimeout) } } import "encoding/json" "math/rand" "net/http" "net/http/pprof" "time" func main () "/log" ":8080" nil func logHandler (w http.ResponseWriter, r *http.Request) make chan int go func () make map string float64 if nil return // simulation of a time consuming process like writing logs into db 400 select case case 200 Compilé y ejecuté el código y le envié algunas cargas usando . Aquí está la salida: hey Summary: Total: 0.8292 secs Slowest: 0.2248 secs Fastest: 0.0039 secs Average: 0.1574 secs Requests/sec: 241.1882Latency distribution: 10% 0.0409 secs 25% 0.1143 secs 50% 0.2007 secs 75% 0.2013 secs 90% 0.2093 secs 95% 0.2187 secs 99% 0.2245 secsStatus code distribution: [200] 87 responses [408] 113 responses in in in in in in in 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 comando o instale para usarlo directamente. go tool pprof pprof 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 , la salida serán los principales consumidores de memoria. top 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 flag o asignándolo directamente dentro del shell atractivo incluirá todos los datos del perfil en el resultado. -nodefraction=0 Diferencia entre plana y acumulativa En el resultado anterior, puede ver dos columnas, y . Plano significa , mientras que cum (acumulativo) significa Para una mejor comprensión, supongamos la siguiente función, el tiempo plano de la función es 4s y el semen es 11s. flat cum solo por esta función por esta función y funciones llamadas en la pila. A { B() DO STH DIRECTLY C() } func A () // takes 1s // takes 4s // 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 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 . también incluye los que se recolectaron 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 dentro del caparazón interactivo pprof: sample_index cantidad de memoria asignada y aún no liberada (montón) inuse_space : cantidad de objetos asignados y aún no liberados (montón) inuse_objects : cantidad total de memoria asignada (independientemente de la liberada) alloc_space : cantidad total de objetos asignados (independientemente de liberados) alloc_objects : 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 se especifica el indicador, pprof inicia un servidor web en el host:por especificado que proporciona una interfaz interactiva basada en web para pprof. -http 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 es un monitoreo de línea de comando para Goroutines. Instalémoslo y ejecútelo en el host que sirve los datos del perfil: grmon go get -u github.com/bcicen/grmon grmon -host :8080 La salida es: parece que bloques de funciones en un 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 a un canal almacenado en búfer (línea 15). Fue el mismo error que mencioné . logHandler chan send ch 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 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ó. SIGPROF Cuando el se llama la función, la 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 para configurar el temporizador de la señal. pprof.StartCPUProfile SIGPROF setitimer(2) En cada señal de invocación, el controlador rastreará la ejecución desviándola del valor de 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. PC 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 . 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. montones El proceso de muestreo se basa en un generador de números pseudoaleatorios basado en una 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. distribución exponencial La frecuencia de muestreo define la media de la distribución exponencial. El valor predeterminado es 512 KB, que está especificado por el . Eso significa que para ajustar el tamaño de la fracción a muestrear, debe cambiar valor. Al establecer el valor en 1, se incluirán todos los bloques asignados en el perfil. Obviamente, esto ralentizará su aplicación. runtime.MemProfileRate runtime.MemProfileRate 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 a 0, desactivará por completo el muestreo de memoria. runtime.MemProfileRate 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í .