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/heap
fü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 gcvis
bekomme ich folgendes:
.. und dieses Heap-Formularprofil:
Hier ist mein Code: https://github.com/sharewind/push-server/blob/v3/broker
performance
memory
go
profile
Sharewind
quelle
quelle
Antworten:
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:
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:
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.
quelle
/debug/pprof/heap
keinen Ausdruck der Laufzeit. MemStats-StrukturAlloc
undHeapAlloc
hat die gleiche Bedeutung.Kurz gesagt: Als Ergänzung zu @Cookie of Nines Antwort können Sie die
--alloc_space
Option ausprobieren .go tool pprof
verwenden--inuse_space
standardmäßig aktiviert . Es tastet die Speichernutzung ab, sodass das Ergebnis eine Teilmenge der realen ist.By
--alloc_space
pprof gibt den gesamten zugewiesenen Speicher seit Programmstart zurück.quelle
--alloc_space
ist genau das, wonach ich gesucht habe.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
undGrafana
inDocker
Containern: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 einInfluxDB
Grafana
Richten (Grafana-Hauptseite -> Obere linke Ecke -> Datenquellen -> Neue Datenquelle hinzufügen):Importieren Sie das Dashboard Nr. 3242 von https://grafana.com (Grafana-Hauptseite -> obere linke Ecke -> Dashboard -> Importieren):
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
Sys
(das Synonim vonRSS
) Kurve ist Kurve ziemlich ähnlichHeapSys
. 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.HeapIdle
wächst mit der gleichen Geschwindigkeit wie aSys
, währendHeapReleased
immer Null ist. Offensichtlich Laufzeit nicht zurück Speicher OS überhaupt , zumindest unter den Bedingungen dieses Tests: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() } }() }
Tatsächlich sparte dieser Ansatz im Vergleich zu den Standardbedingungen etwa 35% des Speichers.
quelle
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
Haftungsausschluss: Ich arbeite für StackImpact
quelle
--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.