Profilierung von Go-Code: Engpässe für Anfänger finden
\n\nWenn Ihr Go-Projekt anfängt zu trödeln, zu viel Speicher zu verbrauchen oder instabil zu laufen, stellt sich die berechtigte Frage: "Wo ist der Engpass?" Intuitive Vermutungen helfen selten – es sind präzise Daten nötig. Profilierung (Profiling) ist der Prozess der Erfassung von Metriken über den Programmablauf: wie viel Zeit jede Funktion benötigt, wie viel Speicher allokiert wird, wie oft der Garbage Collector aufgerufen wird. In diesem Artikel schauen wir uns an, wie Sie mit den integrierten Go-Werkzeugen (pprof und trace) schnell Leistungsprobleme finden und beheben können.
\n\nWas ist Profilierung und warum wird sie benötigt?
\n\nProfilierung ist wie ein Röntgenbild für Ihren Code. Sie sehen nicht nur "das Programm läuft langsam", sondern konkrete Zahlen: Die Funktion parseData() benötigt 40% der Zeit, und loadConfig() allokiert 2 GB Speicher. Für Go gibt es verschiedene Profiltypen:
- \n
- CPU-Profil – zeigt, welche Funktionen Prozessorzeit verbrauchen. \n
- Speicher (Heap)-Profil – zeigt, welche Funktionen Speicher allokieren und wo Objekte leben. \n
- Goroutine-Profil – zeigt die Anzahl aktiver Goroutinen und deren Zustand. \n
- Block-Profil – zeigt, wo Goroutinen an Mutexen oder Kanälen blockiert werden. \n
- Mutex-Profil – zeigt Konkurrenz um Mutexe. \n
Für Anfänger sind CPU- und Speicherprofilierung am nützlichsten. Sie liefern 80% der Antworten auf die Frage "warum der Code trödelt".
\n\nDas Werkzeug pprof: Integrierter Profiler von Go
\n\nGo wird mit dem integrierten Paket net/http/pprof ausgeliefert. Es fügt Ihrem HTTP-Server Endpunkte hinzu, von denen Sie Profile abrufen können. Betrachten wir ein einfaches Beispiel.
Schritt 1: pprof in die Anwendung einbinden
\n\npackage main\n\nimport (\n "fmt"\n "log"\n "net/http"\n _ "net/http/pprof" // Profiler einbinden\n "time"\n)\n\nfunc main() {\n // Server auf Port 8080 starten\n go func() {\n log.Println(http.ListenAndServe("localhost:8080", nil))\n }()\n\n // Unsere "schwere" Geschäftslogik\n for i := 0; i < 100; i++ {\n go heavyTask(i)\n }\n\n // Auf Abschluss warten\n time.Sleep(10 * time.Second)\n fmt.Println("Done")\n}\n\nfunc heavyTask(id int) {\n // Simulation schwerer Arbeit\n for i := 0; i < 1000000; i++ {\n _ = i * i\n }\n fmt.Printf("Task %d finished\\n", id)\n}\n\n\nNach dem Start der Anwendung öffnen Sie den Browser unter http://localhost:8080/debug/pprof/. Sie sehen eine Weboberfläche mit Links zu den Profilen.
Schritt 2: CPU-Profil aufnehmen
\n\nDer einfachste Weg ist die Verwendung der Kommandozeile. Führen Sie aus:
\n\ngo tool pprof http://localhost:8080/debug/pprof/profile?seconds=30\n\nDieser Befehl sammelt 30 Sekunden lang ein CPU-Profil. Nach Abschluss öffnet sich die interaktive pprof-Konsole. Geben Sie top ein, um die "heißesten" Funktionen zu sehen:
(pprof) top\nShowing nodes accounting for 1.50s, 100% of 1.50s total\n flat flat% sum% cum cum%\n 1.50s 100% 100% 1.50s 100% main.heavyTask\n 0 0% 100% 1.50s 100% runtime.main\n\n\nHier ist flat die Zeit, die direkt innerhalb der Funktion verbracht wurde, und cum die Zeit inklusive Aufrufe untergeordneter Funktionen. In unserem Beispiel entfallen 100% der Zeit auf heavyTask.
Schritt 3: Visualisierung über die Weboberfläche
\n\nFür eine anschaulichere Analyse verwenden Sie den Grafikmodus:
\n\ngo tool pprof -http=:8081 http://localhost:8080/debug/pprof/profile?seconds=30\n\nEs öffnet sich ein Browser mit einem interaktiven Aufrufgraphen. Je größer und röter ein Block, desto mehr Ressourcen verbraucht er. Dies ist eine hervorragende Möglichkeit, schnell "fette" Funktionen zu finden.
\n\n