Einleitung: Warum Go-Profilierung für Entwickler ein Muss ist
\nGo ist für seine Leistungsfähigkeit bekannt, aber selbst der schnellste Code kann aufgrund ineffizienter Algorithmen, Speicherlecks oder falscher Handhabung von Goroutinen langsamer werden. Die Profilierung ist der einzige Weg, um genau herauszufinden, wo Ihre Anwendung Ressourcen verbraucht. In diesem Artikel behandeln wir praktische Methoden zur Profilierung von Go-Code mit den integrierten Tools: pprof, trace und benchstat. Sie lernen, Engpässe zu finden und zu beseitigen.
1. CPU-Profilierung mit pprof: Heiße Punkte finden
\nDie CPU-Profilierung zeigt, welche Funktionen die meiste Prozessorzeit verbrauchen. Das integrierte Paket runtime/pprof ermöglicht das einfache Sammeln von Profilen.
1.1. Profilierung zum Code hinzufügen
\nBetrachten wir ein Beispiel eines einfachen HTTP-Servers, der schwere Berechnungen durchführt:
\npackage main\n\nimport (\n "fmt"\n "log"\n "net/http"\n _ "net/http/pprof"\n "os"\n "runtime/pprof"\n)\n\nfunc heavyTask() {\n sum := 0\n for i := 0; i < 1_000_000; i++ {\n sum += i * i\n }\n _ = sum\n}\n\nfunc handler(w http.ResponseWriter, r *http.Request) {\n heavyTask()\n fmt.Fprintf(w, "Done")\n}\n\nfunc main() {\n f, _ := os.Create("cpu.prof")\n pprof.StartCPUProfile(f)\n defer pprof.StopCPUProfile()\n\n http.HandleFunc("/", handler)\n log.Fatal(http.ListenAndServe(":8080", nil))\n}\n\nNach dem Start des Servers und einigen Anfragen erhalten Sie eine Datei cpu.prof. Analysieren Sie sie mit dem Befehl:
go tool pprof -http=:8081 cpu.prof\nIm Browser öffnet sich ein interaktiver Graph. Achten Sie auf die roten Knoten – sie zeigen die "heißesten" Funktionen an. In unserem Fall ist das heavyTask.
1.2. Interpretation der Ergebnisse
\nIn der Weboberfläche von pprof sehen Sie:
\n- \n
- Graph – ein Aufrufgraph mit Knotengrößen proportional zur Ausführungszeit. \n
- Flame Graph – ein "flammenartiger" Graph, bei dem lange Balken die langsamsten Abschnitte darstellen. \n
- Top – eine Tabelle der Funktionen, sortiert nach CPU-Zeit. \n
Wenn Sie sehen, dass heavyTask 90% der Zeit beansprucht, müssen Sie genau diese Funktion optimieren (z.B. die Schleife durch eine Formel für die Summe der Quadrate ersetzen).
2. Speicherprofilierung: Lecks und Allokationen finden
\nSpeicherlecks sind in Go selten, aber übermäßige Allokationen können die Anwendung stark verlangsamen. Wir verwenden runtime/pprof für das Heap-Profil.
2.1. Heap-Profil erfassen
\nFügen wir dem Code die Erfassung des Speicherprofils per SIGUSR1-Signal hinzu:
\nimport (\n "os"\n "os/signal"\n "runtime/pprof"\n "syscall"\n)\n\nfunc main() {\n // ... Ihr Code ...\n sigs := make(chan os.Signal, 1)\n signal.Notify(sigs, syscall.SIGUSR1)\n go func() {\n <-sigs\n f, _ := os.Create("mem.prof")\n pprof.WriteHeapProfile(f)\n f.Close()\n os.Exit(0)\n }()\n // ...\n}\n\nNach dem Senden des Signals kill -SIGUSR1 <PID> erhalten Sie die Datei mem.prof. Analysieren Sie sie:
go tool pprof -http=:8082 mem.prof\n\n2.2. Typische Speicherprobleme
\n- \n
- Große Anzahl kleiner Allokationen – z.B. Erstellung von Strings mit
+anstelle vonstrings.Builder. \n - Halten von Referenzen auf große Objekte – nach der Verwendung sollten Zeiger auf null gesetzt werden. \n
- Goroutinen, die nicht beendet werden – jede Goroutine verbraucht mindestens 4 KB Stack. \n
Beispiel für nicht optimalen Code:
\nfunc badConcat(n int) string {\n s := ""\n for i := 0; i < n; i++ {\n s += "x" // jede Iteration erzeugt einen neuen String\n }\n return s\n}\n\nOptimierung