Wie analysiert man das Golang-Gedächtnis?

78

Ich habe ein Golang-Programm geschrieben, das zur Laufzeit 1,2 GB Speicher verwendet.

Das Aufrufen go tool pprof http://10.10.58.118:8601/debug/pprof/heapführt zu einem Speicherauszug mit nur 323,4 MB Heap-Nutzung.

  • Was ist mit dem Rest der Speichernutzung?
  • Gibt es ein besseres Werkzeug, um den Golang-Laufzeitspeicher zu erklären?

Mit gcvisbekomme ich folgendes:

Geben Sie hier die Bildbeschreibung ein

.. und dieses Heap-Formularprofil:

Geben Sie hier die Bildbeschreibung ein

Hier ist mein Code: https://github.com/sharewind/push-server/blob/v3/broker

Sharewind
quelle
1
Veröffentlichen Sie Ihren Code. Sagen Sie uns, was Ihr Programm macht.
Elithrar
Vielleicht wegen eines GC? dave.cheney.net/2014/07/11/visualising-the-go-garbage-collector könnte helfen.
VonC
Es sieht so aus, als ob der verbleibende Speicher nicht gesammelt und an das System freigegeben wird. Dies geschieht nach einigen Minuten Inaktivität. Warten Sie 8 Minuten und überprüfen Sie erneut. Unter diesem Link finden Sie eine Anleitung zum Debuggen / Profilieren von Go-Programmen: software.intel.com/en-us/blogs/2014/05/10/…
siritinga
Siehe auch runtime.MemStats erklärt unter golang.org/pkg/runtime/#MemStats
Greg Bray

Antworten:

66

Das Heap-Profil zeigt aktiven Speicher an, Speicher, von dem die Laufzeit glaubt, dass er vom go-Programm verwendet wird (dh: wurde vom Garbage Collector nicht erfasst). Wenn der GC Speicher sammelt, wird das Profil verkleinert, aber es wird kein Speicher an das System zurückgegeben . Ihre zukünftigen Zuordnungen werden versuchen, Speicher aus dem Pool zuvor gesammelter Objekte zu verwenden, bevor Sie das System um weitere bitten.

Von außen bedeutet dies, dass die Speichernutzung Ihres Programms entweder zunimmt oder gleich bleibt. Was das externe System als "Resident Size" Ihres Programms darstellt, ist die Anzahl der Bytes RAM, die Ihrem Programm zugewiesen werden, unabhängig davon, ob es verwendete oder gesammelte Go-Werte enthält.

Der Grund, warum diese beiden Zahlen oft sehr unterschiedlich sind, ist folgender:

  1. Der GC-Sammelspeicher hat keinen Einfluss auf die Außenansicht des Programms
  2. Speicherfragmentierung
  3. Der GC wird nur ausgeführt, wenn der verwendete Speicher den nach dem vorherigen GC verwendeten Speicher verdoppelt (standardmäßig siehe: http://golang.org/pkg/runtime/#pkg-overview ).

Wenn Sie genau wissen möchten, wie Go den Speicher sieht, können Sie den Aufruf runtime.ReadMemStats verwenden: http://golang.org/pkg/runtime/#ReadMemStats

Da Sie eine webbasierte Profilerstellung verwenden, wenn Sie über Ihren Browser unter auf die Profildaten zugreifen können, wird durch http://10.10.58.118:8601/debug/pprof/Klicken auf den Heap-Link die Debugging-Ansicht des Heap-Profils angezeigt, in dem eine runtime.MemStats-Struktur ausgedruckt ist Unterseite.

In der Dokumentation zu runtime.MemStats ( http://golang.org/pkg/runtime/#MemStats ) werden alle Felder erläutert. Die interessanten Felder für diese Diskussion sind jedoch:

  • HeapAlloc: Im Wesentlichen das, was der Profiler Ihnen gibt (aktiver Heapspeicher)
  • Alloc: Ähnlich wie HeapAlloc, jedoch für alle verwalteten Speicher
  • Sys: Die vom Betriebssystem angeforderte Gesamtspeichermenge (Adressraum)

Es wird immer noch Diskrepanzen zwischen Sys und den Berichten des Betriebssystems geben, da die Anforderungen von Go an das System und die Angaben des Betriebssystems nicht immer gleich sind. Auch der CGO / Syscall-Speicher (z. B. malloc / mmap) wird nicht von go verfolgt.

Keks der Neun
quelle
Ich verwende go 1.3.3 und die webbasierte Profilerstellung enthält jedoch /debug/pprof/heapkeinen Ausdruck der Laufzeit. MemStats-Struktur
IanB
7
"Es wird kein Speicher an das System zurückgegeben" ist derzeit nicht ganz korrekt. Siehe godoc runtime / debug #FreeOSMemory ().
Jimx
2
Dies könnte in der Vergangenheit anders gewesen, aber nach den aktuellen docs auf runtime.MemStats , Allocund HeapAllochat die gleiche Bedeutung.
Julen
Was bedeutet es, wenn Sys mehr als Resident Memory ist? In meinem Fall beträgt Alloc 778 MB, Sys 2326 MB und Resident Memory 498 MB. Ich kann verstehen, ob der residente Speicher mehr als den Sys-Wert beträgt, da dies bedeutet, dass das Betriebssystem nicht alle angeforderten Programme angegeben hat. Das entgegengesetzte Szenario ist jedoch nicht erklärbar.
Rohanil
31

Kurz gesagt: Als Ergänzung zu @Cookie of Nines Antwort können Sie die --alloc_spaceOption ausprobieren .

go tool pprofverwenden --inuse_spacestandardmäßig aktiviert . Es tastet die Speichernutzung ab, sodass das Ergebnis eine Teilmenge der realen ist.
By --alloc_spacepprof gibt den gesamten zugewiesenen Speicher seit Programmstart zurück.

Menghan
quelle
1
--alloc_spaceist genau das, wonach ich gesucht habe.
Max Malysh
20

Ich war immer verwirrt über das wachsende Wohngedächtnis meiner Go-Anwendungen und musste schließlich die Profiling-Tools lernen, die im Go-Ökosystem vorhanden sind. Runtime bietet viele Metriken innerhalb einer Laufzeit.Memstats- Struktur, aber es ist möglicherweise schwer zu verstehen, welche davon dazu beitragen können, die Gründe für das Speicherwachstum herauszufinden. Daher sind einige zusätzliche Tools erforderlich.

Profiling-Umgebung

Verwenden in Ihrer Anwendung https://github.com/tevjef/go-runtime-metrics . Zum Beispiel können Sie dies in Ihre 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
    }
}

Lauf InfluxDB und Grafanain DockerContainern:

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

Richten Sie die Interaktion zwischen Grafanaund einInfluxDB GrafanaRichten (Grafana-Hauptseite -> Obere linke Ecke -> Datenquellen -> Neue Datenquelle hinzufügen):

Geben Sie hier die Bildbeschreibung ein

Importieren Sie das Dashboard Nr. 3242 von https://grafana.com (Grafana-Hauptseite -> obere linke Ecke -> Dashboard -> Importieren):

Geben Sie hier die Bildbeschreibung ein

Starten Sie abschließend Ihre Anwendung: Sie überträgt Laufzeitmetriken an die Konkurrenten Influxdb. Setzen Sie Ihre Anwendung einer angemessenen Belastung aus (in meinem Fall war sie recht klein - 5 RPS für mehrere Stunden).

Analyse des Speicherverbrauchs

  1. Sys (das Synonim von RSS ) Kurve ist Kurve ziemlich ähnlich HeapSys. Es stellt sich heraus, dass die dynamische Speicherzuweisung der Hauptfaktor für das gesamte Speicherwachstum war, sodass die geringe Menge an Speicher, die von Stapelvariablen verbraucht wird, konstant zu sein scheint und ignoriert werden kann.
  2. Die konstante Menge an Goroutinen garantiert das Fehlen von Goroutinenlecks / Stapelvariablenlecks;
  3. Die Gesamtmenge der zugewiesenen Objekte bleibt während der Lebensdauer des Prozesses gleich (es macht keinen Sinn, die Schwankungen zu berücksichtigen).
  4. Die überraschendste Tatsache: HeapIdlewächst mit der gleichen Geschwindigkeit wie a Sys, während HeapReleasedimmer Null ist. Offensichtlich Laufzeit nicht zurück Speicher OS überhaupt , zumindest unter den Bedingungen dieses Tests:
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.

Geben Sie hier die Bildbeschreibung einGeben Sie hier die Bildbeschreibung ein

Für diejenigen, die versuchen, das Problem des Speicherverbrauchs zu untersuchen, würde ich empfehlen, die beschriebenen Schritte zu befolgen, um einige triviale Fehler (wie Goroutine-Leck) auszuschließen.

Speicher explizit freigeben

Es ist interessant, dass man den Speicherverbrauch durch explizite Aufrufe an debug.FreeOSMemory():

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

Vergleich

Tatsächlich sparte dieser Ansatz im Vergleich zu den Standardbedingungen etwa 35% des Speichers.

Vitaly Isaev
quelle
7

Sie können auch StackImpact verwenden , das automatisch reguläre und durch Anomalien ausgelöste Speicherzuordnungsprofile aufzeichnet und an das Dashboard meldet, die in historischer und vergleichbarer Form verfügbar sind. Weitere Informationen finden Sie in diesem Blogbeitrag. Erkennung von Speicherlecks in Production Go-Anwendungen

Geben Sie hier die Bildbeschreibung ein

Haftungsausschluss: Ich arbeite für StackImpact

logix
quelle
Ich habe StackImpact ausprobiert und Speicherlecks sind enorm gewachsen. Einer der Speicherleckpunkte pastebin.com/ZAPCeGmp
Vlad
Es sieht so aus, als würden Sie verwenden --alloc_space, was nicht für die Erkennung von Speicherverlusten geeignet ist. Es zeigt Ihnen nur, wie viel Speicher seit dem Programmstart zugewiesen wurde. Für ein lang laufendes Programm können die Zahlen ziemlich hoch werden. Bisher sind uns keine Speicherverluste im StackImpact-Agenten bekannt.
Logix