performance - cómo analizar la memoria golang
memory profile (4)
Escribí un programa de golang, costó 1.2GB en el tiempo de ejecución. cuando uso
go tool pprof http://10.10.58.118:8601/debug/pprof/heap
para obtener un tugurio.
Muestra solo 323.4MB en el montón. ¿qué otra memoria se ha ido?
¿Hay alguna herramienta mejor para explicar la memoria de tiempo de ejecución de golang?
cuando uso gcvis tengo esto
y el perfil de la forma de montón
mi código en
https://github.com/sharewind/push-server/blob/v3/broker
Como complemento a la respuesta de @Cookie of Nine, en resumen: puedes probar la opción --alloc_space
.
go tool pprof
uso de la go tool pprof
por defecto. Mide el uso de memoria, por lo que el resultado es un subconjunto de uno real.
Por --alloc_space
pprof devuelve toda la memoria asignada desde que se inició el programa.
El perfil de montón muestra la memoria activa, memoria que el tiempo de ejecución cree que está siendo utilizada por el programa go (es decir: no ha sido recolectada por el recolector de basura). Cuando el GC recopila memoria, el perfil se reduce, pero no se devuelve ninguna memoria al sistema . Sus futuras asignaciones intentarán usar la memoria del grupo de objetos recolectados anteriormente antes de pedirle al sistema más.
Desde el exterior, esto significa que el uso de la memoria de su programa aumentará o se mantendrá nivelado. Lo que el sistema externo presenta como el "Tamaño del residente" de su programa es la cantidad de bytes de RAM asignados a su programa, ya sea que contenga valores en uso o recolectados.
La razón por la cual estos dos números son a menudo bastante diferentes es porque:
- La memoria de recopilación GC no tiene ningún efecto en la vista exterior del programa
- Fragmentación de la memoria
- El GC solo se ejecuta cuando la memoria en uso duplica la memoria en uso después del GC anterior (de forma predeterminada, consulte: http://golang.org/pkg/runtime/#pkg-overview )
Si desea un desglose preciso de cómo Go ve la memoria, puede usar la llamada runtime.ReadMemStats: http://golang.org/pkg/runtime/#ReadMemStats
Alternativamente, dado que está utilizando perfiles basados en la web si puede acceder a los datos de creación de perfiles a través de su navegador en: http://10.10.58.118:8601/debug/pprof/
, al hacer clic en el enlace del montón le mostrará la vista de depuración del montón perfil, que tiene una copia impresa de una estructura runtime.MemStats en la parte inferior.
La documentación de runtime.MemStats ( http://golang.org/pkg/runtime/#MemStats ) tiene la explicación de todos los campos, pero los más interesantes para esta discusión son:
- HeapAlloc: esencialmente lo que el generador de perfiles le está dando (memoria de pila activa)
- Alloc: similar a HeapAlloc, pero para todos va la memoria administrada
- Sys: la cantidad total de memoria (espacio de direcciones) solicitada desde el SO
Todavía habrá discrepancias entre Sys y lo que informa el sistema operativo porque lo que Go le pide al sistema y lo que el sistema operativo le proporciona no siempre es lo mismo. También la memoria CGO / syscall (por ejemplo: malloc / mmap) no se rastrea mediante go.
Siempre estaba confundido acerca de la creciente memoria residencial de mis aplicaciones Go, y finalmente tuve que aprender las herramientas de creación de perfiles que están presentes en el ecosistema Go. Runtime proporciona muchas métricas dentro de un golang.org/pkg/runtime/#MemStats estructura, pero puede ser difícil entender cuál de ellas puede ayudar a descubrir las razones del crecimiento de la memoria, por lo que se necesitan algunas herramientas adicionales.
Entorno de creación de perfiles
Use https://github.com/tevjef/go-runtime-metrics en su aplicación. Por ejemplo, puedes poner esto en tu main
:
import(
metrics "github.com/tevjef/go-runtime-metrics"
)
func main() {
//...
metrics.DefaultConfig.CollectionInterval = time.Second
if err := metrics.RunCollector(metrics.DefaultConfig); err != nil {
// handle error
}
}
Ejecute InfluxDB
y Grafana
dentro de contenedores Docker
:
docker run --name influxdb -d -p 8086:8086 influxdb
docker run -d -p 9090:3000/tcp --link influxdb --name=grafana grafana/grafana:4.1.0
Configure la interacción entre Grafana
e InfluxDB
Grafana
(página principal de Grafana -> esquina superior izquierda -> fuentes de datos -> agregar nueva fuente de datos):
Panel de importación #3242 desde https://grafana.com (página principal de Grafana -> esquina superior izquierda -> Panel de control -> Importar):
Finalmente, inicie su aplicación: transmitirá las métricas de tiempo de ejecución al Influxdb
. Ponga su aplicación a una carga razonable (en mi caso, fue bastante pequeña, 5 RPS durante varias horas).
Análisis de consumo de memoria
-
Sys
curvaSys
(el sinónimo deRSS
) es bastante similar a la curvaHeapSys
. Resulta que la asignación de la memoria dinámica fue el factor principal del crecimiento general de la memoria, por lo que la pequeña cantidad de memoria consumida por las variables de la pila parece ser constante y puede ignorarse; - La cantidad constante de goroutines garantiza la ausencia de fuga de rutinas / fuga de variables de pila;
- La cantidad total de objetos asignados sigue siendo la misma (no tiene sentido tener en cuenta las fluctuaciones) durante la vida útil del proceso.
- El hecho más sorprendente:
HeapIdle
está creciendo con la misma velocidad que unSys
, mientras queHeapReleased
siempre es cero. Obviamente, el tiempo de ejecución no devuelve la memoria al sistema operativo en absoluto , al menos en las condiciones de esta prueba:
HeapIdle minus HeapReleased estimates the amount of memory that could be returned to the OS, but is being retained by the runtime so it can grow the heap without requesting more memory from the OS.
Para aquellos que están tratando de investigar el problema del consumo de memoria, recomendaría seguir los pasos descritos para excluir algunos errores triviales (como fuga de goroutine).
Liberando memoria explícitamente
Es interesante que uno puede disminuir significativamente el consumo de memoria con llamadas explícitas a la debug.FreeOSMemory()
:
// in the top-level package
func init() {
go func() {
t := time.Tick(time.Second)
for {
<-t
debug.FreeOSMemory()
}
}()
}
De hecho, este enfoque ahorró aproximadamente el 35% de la memoria en comparación con las condiciones predeterminadas.
Ver también
También puede usar StackImpact , que registra e informa automáticamente los perfiles de asignación de memoria activados por anomalías y regulares en el tablero, que están disponibles en una forma histórica y comparable. Consulte esta publicación en el blog para obtener más información. Detección de fugas de memoria en aplicaciones de producción Go
Descargo de responsabilidad: yo trabajo para StackImpact