it-swarm.com.de

Wie kann man das Gedächtnis von Golang analysieren?

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

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

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

Mit gcvis bekomme ich folgendes:

enter image description here

.. und dieses Heap-Formularprofil:

enter image description here

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

61
sharewind

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

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

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

  1. Der GC-Erfassungsspeicher hat keine Auswirkung 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

Alternativ können Sie, da Sie webbasiertes Profiling verwenden, über Ihren Browser auf die Profiling-Daten zugreifen: http://10.10.58.118:8601/debug/pprof/ Wenn Sie auf den Heap-Link klicken, wird die Debug-Ansicht des Heap-Profils angezeigt, in der unten die Struktur "runtime.MemStats" ausgedruckt ist.

In der Dokumentation zu runtime.MemStats ( http://golang.org/pkg/runtime/#MemStats ) sind alle Felder erklärt, aber die für diese Diskussion interessanten sind:

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

Es wird immer noch Unstimmigkeiten zwischen Sys und dem geben, was das Betriebssystem meldet, da die Go-Anforderungen des Systems und die des Betriebssystems nicht immer gleich sind. Auch CGO/Syscall-Speicher (zB: malloc/mmap) wird von go nicht nachverfolgt.

53
Cookie of Nine

Als Ergänzung zu @Cookie of Nines Antwort, kurz gesagt: Sie können das --alloc_space Möglichkeit.

go tool pprof verwenden --inuse_space standardmäßig. Es wird die Speichernutzung abgetastet, sodass das Ergebnis eine Teilmenge der realen ist.
Durch --alloc_space pprof gibt den gesamten zugewiesenen Speicher seit dem Programmstart zurück.

25
menghan

Ich war immer verwirrt über das wachsende Arbeitsgedächtnis meiner Go-Anwendungen, und schließlich musste ich lernen, welche Profilerstellungstools im Go-Ökosystem vorhanden sind. Runtime bietet viele Metriken in einer runtime.Memstats -Struktur, aber es ist möglicherweise schwer zu verstehen, welche von ihnen helfen können, die Gründe für das Speicherwachstum herauszufinden. Daher sind einige zusätzliche Tools erforderlich.

Profilerstellungsumgebung

Verwenden Sie https://github.com/tevjef/go-runtime-metrics in Ihrer Anwendung. Zum Beispiel können Sie dies in Ihre main einfügen:

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
    }
}

Führen Sie InfluxDB und Grafana in Docker Containern aus:

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 Grafana und InfluxDBGrafana ein (Grafana-Hauptseite -> Linke obere Ecke -> Datenquellen -> Neue Datenquelle hinzufügen):

enter image description here

Importiere Dashboard # 3242 von https://grafana.com (Grafana Hauptseite -> Linke obere Ecke -> Dashboard -> Importieren):

enter image description here

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

Speicherverbrauchsanalyse

  1. Die Kurve Sys (Synonym für RSS) ist der Kurve HeapSys ziemlich ähnlich. Es stellt sich heraus, dass die dynamische Speicherzuweisung der Hauptfaktor für das allgemeine Speicherwachstum war, sodass die geringe Menge an Speicher, die von Stapelvariablen belegt wird, konstant zu sein scheint und ignoriert werden kann.
  2. Die konstante Menge an Goroutinen garantiert die Abwesenheit von Goroutinenlecks/Leckage von Stapelvariablen.
  3. Die Gesamtmenge der zugewiesenen Objekte bleibt während der gesamten Lebensdauer des Prozesses gleich (es macht keinen Sinn, die Schwankungen zu berücksichtigen).
  4. Die überraschendste Tatsache: HeapIdle wächst mit der gleichen Rate wie ein Sys, während HeapReleased immer Null ist . Offensichtlich gibt die Laufzeit keinen Speicher an das Betriebssystem zurück ü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.

enter image description hereenter image description here

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

Speicher explizit freigeben

Es ist interessant, dass man mit expliziten Aufrufen von debug.FreeOSMemory() den Speicherverbrauch deutlich senken kann:

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

comparison

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

13
Vitaly Isaev

Sie können auch StackImpact verwenden, um reguläre und durch Anomalien ausgelöste Speicherzuordnungsprofile automatisch aufzuzeichnen und an das Dashboard zu melden, die in einer historischen und vergleichbaren Form verfügbar sind. Weitere Informationen finden Sie in diesem Blogeintrag Speicherleckerkennung in Production Go-Anwendungen

enter image description here

Haftungsausschluss: Ich arbeite für StackImpact

5
logix