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 Code is a Must-Have

Development in Go (Golang) is renowned for its performance and efficiency. However, even the fastest code can slow down if you don't account for the specifics of working with memory, goroutines, or input/output. Profiling is the process of collecting data about a program's execution, allowing you to look under the hood and understand which functions consume the most CPU, memory, or time.

In this article, we will explore practical profiling tools in Go: pprof, trace, and built-in benchmarks. You will learn how to find bottlenecks and optimize code using real-world examples.



1. Core Profiling Tools in Golang

Go provides a powerful arsenal for profiling right out of the box. Let's look at three key approaches:

  • pprof — profiling of CPU, memory, blocking, and goroutines.
  • trace — event tracing: goroutine creation, system calls, GC.
  • benchmark — built-in performance testing using go test -bench.

To get started with pprof, simply import net/http/pprof into your HTTP server or use runtime/pprof for CLI applications.



1.1. How to Start CPU Profiling

The easiest way is to add an endpoint to your HTTP server:

package main

import ( "log" "net/http" _ "net/http/pprof")

func main() { go func() { log.Println(http.ListenAndServe("localhost:6060", nil)) }() // your main code select {}}

After starting, open http://localhost:6060/debug/pprof/ — you will see a dashboard. To collect a CPU profile for 30 seconds, run:

go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30

This will download the data and open the interactive pprof console.



2. Finding Bottlenecks with pprof: CPU and Memory

Suppose we have a function that processes a large data array. We suspect it is slow. Let's write a test example and profile it.



2.1. Example of Slow Code

package main

import ( "fmt" "time")

func slowFunction(data []int) int { sum := 0 for _, v := range data { time.Sleep(1 * time.Microsecond) // simulating work sum += v * v } return sum}

func main() { data := make([]int, 100000) for i := range data { data[i] = i } result := slowFunction(data) fmt.Println(result)}

Let's run CPU profiling:

go run main.go &go tool pprof -http=:8080 http://localhost:6060/debug/pprof/profile?seconds=10

A visualization will open in the browser — a call graph. The thickness of the edges shows how much time each function took. We will immediately see that slowFunction and time.Sleep are the main CPU consumers.



2.2. Memory Analysis (heap profile)

To find memory leaks or objects that take up too much space, use:

go tool pprof http://localhost:6060/debug/pprof/heap

In the interactive console, you can run the top command:

(pprof) top10Showing nodes accounting for 80MB, 100% of 80MB total      flat  flat%   sum%        cum   cum%      40MB 50.00% 50.00%       40MB 50.00%  main.slowFunction      20MB 25.00% 75.00%       20MB 25.00%  main.createLargeSlice      20MB 25.00% 100.00%      20MB 25.00%  runtime.makeslice

This shows which functions allocate the most memory. If you see that main.slowFunction allocates 40MB, you should consider refactoring — for example, reusing slices

Blogs

Book Recommendations