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=30This 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=10A 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/heapIn 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.makesliceThis shows which functions allocate the most memory. If you see that main.slowFunction allocates 40MB, you should consider refactoring — for example, reusing slices