Profiling Go Code: Finding Bottlenecks in Practice

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Introduction: Why Profiling Go is a Must-Have for Developers

\n

Go is renowned for its performance, but even the fastest code can slow down due to inefficient algorithms, memory leaks, or improper goroutine management. Profiling is the only way to know exactly where your application is spending resources. In this article, we will explore practical methods for profiling Go code using built-in tools: pprof, trace, and benchstat. You will learn how to find bottlenecks and eliminate them.

\n\n

1. CPU Profiling with pprof: Finding Hot Spots

\n

CPU profiling shows which functions consume the most processor time. The built-in runtime/pprof package makes it easy to collect profiles.

\n\n

1.1. Adding Profiling to Code

\n

Consider an example of a simple HTTP server that performs heavy computations:

\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

After starting the server and making a few requests, you will have a cpu.prof file. Analyze it with the command:

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

An interactive graph will open in the browser. Pay attention to the red nodes — they indicate the "hottest" functions. In our case, it is heavyTask.

\n\n

1.2. Interpreting Results

\n

In the pprof web interface, you will see:

\n
    \n
  • Graph — a call graph with node sizes proportional to execution time.
  • \n
  • Flame Graph — a flame graph where long bars represent the slowest sections.
  • \n
  • Top — a table of functions sorted by CPU time.
  • \n
\n

If you see that heavyTask takes 90% of the time, it means you need to optimize it (for example, replace the loop with the sum of squares formula).

\n\n

2. Memory Profiling: Finding Leaks and Allocations

\n

Memory leaks in Go are rare, but excessive allocations can significantly slow down an application. We use runtime/pprof for the heap profile.

\n\n

2.1. Collecting a Heap Profile

\n

Let's add memory profile collection triggered by the SIGUSR1 signal:

\n
import (\n    "os"\n    "os/signal"\n    "runtime/pprof"\n    "syscall"\n)\n\nfunc main() {\n    // ... your 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

After sending the signal kill -SIGUSR1 <PID>, you will get the mem.prof file. Analyze it:

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

2.2. Typical Memory Issues

\n
    \n
  • Large number of small allocations — for example, creating strings with + instead of strings.Builder.
  • \n
  • Holding references to large objects — pointers should be set to nil after use.
  • \n
  • Goroutines that never finish — each goroutine consumes at least 4 KB of stack.
  • \n
\n

Example of suboptimal code:

\n
func badConcat(n int) string {\n    s := ""\n    for i := 0; i < n; i++ {\n        s += "x"  // each iteration creates a new string\n    }\n    return s\n}\n
\n

Optimiz

Blogs

Book Recommendations