Introduction: Why Profiling Go is a Must-Have for Developers
\nGo 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.
1. CPU Profiling with pprof: Finding Hot Spots
\nCPU profiling shows which functions consume the most processor time. The built-in runtime/pprof package makes it easy to collect profiles.
1.1. Adding Profiling to Code
\nConsider an example of a simple HTTP server that performs heavy computations:
\npackage 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\nAfter starting the server and making a few requests, you will have a cpu.prof file. Analyze it with the command:
go tool pprof -http=:8081 cpu.prof\nAn interactive graph will open in the browser. Pay attention to the red nodes — they indicate the "hottest" functions. In our case, it is heavyTask.
1.2. Interpreting Results
\nIn 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
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).
2. Memory Profiling: Finding Leaks and Allocations
\nMemory leaks in Go are rare, but excessive allocations can significantly slow down an application. We use runtime/pprof for the heap profile.
2.1. Collecting a Heap Profile
\nLet's add memory profile collection triggered by the SIGUSR1 signal:
\nimport (\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\nAfter sending the signal kill -SIGUSR1 <PID>, you will get the mem.prof file. Analyze it:
go tool pprof -http=:8082 mem.prof\n\n2.2. Typical Memory Issues
\n- \n
- Large number of small allocations — for example, creating strings with
+instead ofstrings.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
Example of suboptimal code:
\nfunc 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\nOptimiz