Profiling Go Code: Finding Bottlenecks for Beginners

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

Profiling Go Code: Finding Bottlenecks for Beginners

\n\n

When your Go project starts to slow down, consume too much memory, or work unstably, a natural question arises: "Where is the bottleneck?" Intuitive guesses rarely help — you need precise data. Profiling is the process of collecting metrics about a program's operation: how much time each function takes, how much memory is allocated, how often the garbage collector is called. In this article, we'll explore how to quickly find and fix performance issues using Go's built-in tools (pprof and trace).

\n\n

What is profiling and why is it needed?

\n\n

Profiling is like an X-ray for your code. You don't just see "the program is running slowly," but specific numbers: the parseData() function takes 40% of the time, and loadConfig() allocates 2 GB of memory. For Go, there are several types of profiles:

\n\n
    \n
  • CPU profile — shows which functions consume processor time.
  • \n
  • Memory (heap) profile — shows which functions allocate memory and where objects live.
  • \n
  • Goroutine profile — shows the number of active goroutines and their state.
  • \n
  • Block profile — shows where goroutines are blocked on mutexes or channels.
  • \n
  • Mutex profile — shows contention for mutexes.
  • \n
\n\n

For beginners, CPU and Memory profiling will be the most useful. They provide 80% of the answers to the question "why is the code slow."

\n\n

The pprof tool: Go's built-in profiler

\n\n

Go comes with the built-in net/http/pprof package. It adds endpoints to your HTTP server from which you can collect profiles. Let's look at a basic example.

\n\n

Step 1: Adding pprof to the application

\n\n
package main\n\nimport (\n    "fmt"\n    "log"\n    "net/http"\n    _ "net/http/pprof" // import the profiler\n    "time"\n)\n\nfunc main() {\n    // Start the server on port 8080\n    go func() {\n        log.Println(http.ListenAndServe("localhost:8080", nil))\n    }()\n\n    // Our "heavy" business logic\n    for i := 0; i < 100; i++ {\n        go heavyTask(i)\n    }\n\n    // Wait for completion\n    time.Sleep(10 * time.Second)\n    fmt.Println("Done")\n}\n\nfunc heavyTask(id int) {\n    // Simulate heavy work\n    for i := 0; i < 1000000; i++ {\n        _ = i * i\n    }\n    fmt.Printf("Task %d finished\\n", id)\n}\n
\n\n

After starting the application, open your browser at http://localhost:8080/debug/pprof/. You will see a web interface with links to profiles.

\n\n

Step 2: Collecting a CPU profile

\n\n

The simplest way is to use the command line. Run:

\n\n
go tool pprof http://localhost:8080/debug/pprof/profile?seconds=30
\n\n

This command will collect a CPU profile for 30 seconds. After completion, an interactive pprof console will open. Type top to see the "hottest" functions:

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

Here, flat is the time spent directly inside the function, and cum is the time including calls to child functions. In our example, 100% of the time is spent on heavyTask.

\n\n

Step 3: Visualization via web interface

\n\n

For a more visual analysis, use the graphical mode:

\n\n
go tool pprof -http=:8081 http://localhost:8080/debug/pprof/profile?seconds=30
\n\n

A browser will open with an interactive call graph. The larger and redder the block, the more resources it consumes. This is an excellent way to quickly find "heavy" functions.

\n\n

Memory profiling:

Blogs

Book Recommendations