2014-07-21 17 views
34

Ho scritto un programma golang, è costato 1,2 GB in fase di esecuzione. quando usocome analizzare la memoria golang

go tool pprof http://10.10.58.118:8601/debug/pprof/heap 

a ottenuto una discarica.

Si mostrano solo 323.4MB nel mucchio. cos'è andato a finire l'altra memoria?

C'è uno strumento migliore per spiegare la memoria di runtime golang?


quando uso gcvis ho ottenuto questo profilo

enter image description here

e la forma mucchio enter image description here

il mio codice su

https://github.com/sharewind/push-server/blob/v3/broker 
+1

Pubblica il tuo codice. Dicci cosa fa il tuo programma. – elithrar

+0

Forse a causa di un gc? http://dave.cheney.net/2014/07/11/visualising-the-go-garbage-collector potrebbe aiutare. – VonC

+0

Sembra che la memoria rimanente non sia garbaged raccolta e rilasciata al sistema. È fatto dopo pochi minuti di inattività. Attendere 8 minuti e ricontrollare. Controlla questo link per una guida su come eseguire il debug/profilo Vai programmi: https://software.intel.com/en-us/blogs/2014/05/10/debugging-performance-issues-in-go-programs – siritinga

risposta

33

L'heap profilo mostra memoria attiva , memoria t runtime crede che sia in uso dal programma go (cioè: non è stato raccolto dal garbage collector). Quando il GC raccoglie memoria, il profilo si restringe, ma non viene restituita memoria al sistema. Le tue allocazioni future proveranno a utilizzare la memoria dal pool di oggetti precedentemente raccolti prima di chiedere al sistema altro.

Dall'esterno, questo significa che l'utilizzo della memoria del programma aumenterà o resterà a livello. Quello che il sistema esterno presenta come "Dimensione residente" del tuo programma è il numero di byte di RAM assegnati al tuo programma, sia che mantenga valori di uso in corso o raccolti.

Il motivo per cui questi due numeri sono spesso molto diversi sono causa:

  1. Il GC memoria la raccolta non ha alcun effetto sulla vista al di fuori del programma
  2. memoria frammentazione
  3. Il GC viene eseguito solo quando la memoria in uso raddoppia la memoria in uso dopo il GC precedente (per impostazione predefinita, vedere: http://golang.org/pkg/runtime/#pkg-overview)

Se si desidera un'analisi accurata di come vedere s la memoria è possibile utilizzare le runtime.ReadMemStats chiamano: http://golang.org/pkg/runtime/#ReadMemStats

In alternativa, dal momento che si sta utilizzando profiling basato sul web se è possibile accedere ai dati di profiling tramite il browser all'indirizzo: http://10.10.58.118:8601/debug/pprof/, cliccando sul link mucchio vi mostrerà il debug vista del profilo heap, che ha una stampa di una struttura runtime.MemStats in basso.

Il runtime.MemStats documentazione (http://golang.org/pkg/runtime/#MemStats) ha la spiegazione di tutti i campi, ma quelli interessanti per questa discussione sono:

  • HeapAlloc: in sostanza, ciò che il profiler ti dà (memoria heap attivo)
  • Alloc: simile a HeapAlloc , ma per tutti andare memoria gestita
  • Sys: la quantità totale di memoria (spazio di indirizzamento) richiesto dal sistema operativo

ci sarà ancora discrepanze tra Sys, e quali sono le relazioni OS perché ciò che Go chiede al sistema, e cosa OS g I suoi non sono sempre gli stessi. Anche CGO/syscall (ad es: malloc/mmap) di memoria non è monitorato da andare.

+0

Sto usando go 1.3.3 e profilazione web-based tuttavia '/ debug/pprof/heap' non contiene una stampa del runtime.MemStats struct – IanB

+6

"Nessuna memoria viene restituita al sistema" non è completamente precisa ora. Vedi godoc runtime/debug #FreeOSMemory(). – jimx

+1

Questo potrebbe essere stato diverso in passato, ma secondo i documenti attuali su [runtime.MemStats] (https://golang.org/pkg/runtime/#MemStats), 'Alloc' e' HeapAlloc' hanno lo stesso significato . – julen

18

In aggiunta alla risposta @Cookie di Nove di, insomma: si può provare l'opzione --alloc_space.

go tool pprof uso --inuse_space per impostazione predefinita. Analizza l'utilizzo della memoria in modo che il risultato sia sottoinsieme di quello reale.
Con --alloc_space pprof restituisce tutti alloced memoria dal programma è stato avviato.

+1

'--alloc_space' è esattamente quello che stavo cercando. –

2

È inoltre possibile utilizzare StackImpact, che registra e registra automaticamente profili di allocazione di memoria regolari e con anomalie sulla dashboard, disponibili in una forma storica e comparabile. Vedi questo post sul blog per maggiori dettagli Memory Leak Detection in Production Go Applications

enter image description here

Disclaimer: io lavoro per StackImpact

+0

Ho provato StackImpact e le perdite di memoria sono cresciute tremendamente. Uno dei punti di perdita di memoria https://pastebin.com/ZAPCeGmp – Vlad

+0

Sembra che tu stia usando '--alloc_space', che non è adatto per il rilevamento di perdite di memoria. Ti mostrerà solo quanta memoria è stata allocata dall'inizio del programma. Per un programma a lunga corsa i numeri possono diventare piuttosto alti. Fino ad ora non siamo a conoscenza di perdite di memoria nell'agente StackImpact. – logix

4

ero sempre confuso circa il crescente di memoria residenziale di mie applicazioni Go, e alla fine ho dovuto imparare gli strumenti di profiling che sono presenti nell'ecosistema Go. Runtime fornisce molte metriche all'interno di una struttura runtime.Memstats, ma può essere difficile da capire quali di esse può aiutare a scoprire le ragioni della crescita di memoria, quindi sono necessari alcuni strumenti aggiuntivi.

Profiling ambiente

Uso https://github.com/tevjef/go-runtime-metrics nell'applicazione. Per esempio, si può mettere questo nel vostro 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 
    } 
} 

Run InfluxDB e Grafana entro Docker contenitori:

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 

Impostare interazione tra Grafana e InfluxDBGrafana (pagina principale Grafana -> angolo alto a sinistra -> Origini dati -> Aggiungi nuova origine dati:

enter image description here

Importa cruscotto #3242 da https://grafana.com (pagina principale Grafana -> Top sinistro -> Dashboard -> Importa):

enter image description here

Infine, lanciare la vostra applicazione: trasmetterà metriche di runtime al contenerized Influxdb.Metti la tua applicazione sotto un carico ragionevole (nel mio caso era piuttosto piccola - 5 RPS per diverse ore).

consumo di memoria analisi

  1. Sys (il sinonimo di RSS) curva è molto simile a HeapSys curva. Risulta che l'allocazione dinamica della memoria era il fattore principale della crescita complessiva della memoria, quindi la piccola quantità di memoria consumata dalle variabili dello stack sembra essere costante e può essere ignorata;
  2. La quantità costante di goroutine garantisce l'assenza di perdite di goroutine/perdita di variabili dello stack;
  3. La quantità totale di oggetti allocati rimane la stessa (non è necessario tenere conto delle fluttuazioni) durante la vita del processo.
  4. Il fatto più sorprendente: HeapIdle cresce con la stessa velocità di Sys, mentre HeapReleased è sempre zero. Ovviamente runtime non restituisce la memoria di OS affatto, almeno nelle condizioni di questo test:
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. 

enter image description hereenter image description here

Per chi sta cercando di studiare il problema del consumo di memoria raccomando di seguire i passaggi descritti per escludere alcuni errori banali (come la perdita di goroutine).

memoria Liberare esplicitamente

E 'interessante il fatto che l'uno può ridurre in modo significativo il consumo di memoria con le chiamate esplicite a debug.FreeOSMemory():

// in the top-level package 
func init() { 
    go func() { 
     t := time.Tick(time.Second) 
     for { 
      <-t 
      debug.FreeOSMemory() 
     } 
    }() 
} 

comparison

In realtà, questo approccio ha salvato circa il 35% di memoria rispetto alle condizioni predefinite.

Vedi anche

https://github.com/golang/go/issues/14521