Profilierung von Go-Code: Engpässe in der Praxis finden

Online Python-Trainer für Anfänger

Lernen Sie Python einfach ohne theoretische Überlastung. Lösen Sie praktische Aufgaben mit automatischer Überprüfung und schreiben Sie Code direkt im Browser.

Kurs starten

Einleitung: Warum Go-Profilierung für Entwickler ein Muss ist

\n

Go 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.

\n\n

1. CPU-Profilierung mit pprof: Heiße Punkte finden

\n

Die CPU-Profilierung zeigt, welche Funktionen die meiste Prozessorzeit verbrauchen. Das integrierte Paket runtime/pprof ermöglicht das einfache Sammeln von Profilen.

\n\n

1.1. Profilierung zum Code hinzufügen

\n

Betrachten wir ein Beispiel eines einfachen HTTP-Servers, der schwere Berechnungen durchführt:

\n
package 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
\n

Nach dem Start des Servers und einigen Anfragen erhalten Sie eine Datei cpu.prof. Analysieren Sie sie mit dem Befehl:

\n
go tool pprof -http=:8081 cpu.prof
\n

Im 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.

\n\n

1.2. Interpretation der Ergebnisse

\n

In 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
\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).

\n\n

2. Speicherprofilierung: Lecks und Allokationen finden

\n

Speicherlecks sind in Go selten, aber übermäßige Allokationen können die Anwendung stark verlangsamen. Wir verwenden runtime/pprof für das Heap-Profil.

\n\n

2.1. Heap-Profil erfassen

\n

Fügen wir dem Code die Erfassung des Speicherprofils per SIGUSR1-Signal hinzu:

\n
import (\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
\n

Nach dem Senden des Signals kill -SIGUSR1 <PID> erhalten Sie die Datei mem.prof. Analysieren Sie sie:

\n
go tool pprof -http=:8082 mem.prof
\n\n

2.2. Typische Speicherprobleme

\n
    \n
  • Große Anzahl kleiner Allokationen – z.B. Erstellung von Strings mit + anstelle von strings.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
\n

Beispiel für nicht optimalen Code:

\n
func 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
\n

Optimierung

Blogs

Buchempfehlungen