Your Go code running slow? Time to look under the hood
\n\nYou wrote a service in Go. It works. But when the load reaches 10,000 RPS, the server starts to "choke." Sound familiar? Go is famous for its "out of the box" performance, but even this elegant language has its pitfalls. Goroutine leaks, inefficient memory management, unnecessary allocations — all of this kills speed.
\n\nIn this article, we'll break down how Go profiling helps find bottlenecks and show specific techniques for optimizing Go applications. You'll learn how to turn your application into a rocket using the built-in pprof and go test -bench tools. Let's go!
\n\nProfiler toolkit: pprof and trace
\n\nGo provides two powerful tools for performance analysis: pprof (CPU, memory, and lock profiling) and go tool trace (concurrency and GC event analysis).
Embedding pprof into an HTTP server
\n\nThe easiest way to start profiling is to add net/http/pprof to your server. This takes exactly one import line:
import (\n _ "net/http/pprof"\n "net/http"\n)\n\nfunc main() {\n http.ListenAndServe(":8080", nil)\n}\n\nNow profiles are available at /debug/pprof/. To capture a CPU profile for 30 seconds, run:
go tool pprof http://localhost:8080/debug/pprof/profile?seconds=30\n\nAfter data collection, an interactive console starts. The top15 command will show the hottest functions:
(pprof) top15\nShowing nodes accounting for 3.50s, 87.50% of 4.00s total\n flat flat% sum% cum cum%\n 1.20s 30.00% 30.00% 1.50s 37.50% main.heavyComputation\n\nSee main.heavyComputation? That's our candidate for optimization. For visualization, use web (if Graphviz is installed) — you'll get a call graph.
Memory profiling: finding leaks
\n\nMemory leaks in Go are most often goroutine leaks or forgotten references to large objects. Let's capture a heap profile:
\n\ngo tool pprof http://localhost:8080/debug/pprof/heap\n\nThe alloc_objects command will show which functions create the most objects:
(pprof) alloc_objects\n 2.50GB 100% 100% 2.50GB 100% main.createLargeSlice\n\nThat's how we found a function allocating 2.5 GB of memory! Perhaps it's creating a huge slice that could be reused with sync.Pool.
Writing benchmarks: your first step to optimization
\n\nBefore optimizing, you need to measure. Go benchmarks are the standard tool for measuring the performance of individual functions.
\n\nExample: comparing string concatenation
\n\nLet's say we're concatenating strings in a loop. The bad way:
\n\nfunc ConcatSlow(parts []string) string {\n result := ""\n for _, p := range parts {\n result += p\n }\n return result\n}\n\nThe good way — use strings.Builder:
func ConcatFast(parts []string) string {\n var b strings.Builder\n for _, p := range parts {\n b.WriteString(p)\n }\n return b.String()\n}\n\nLet's write a benchmark (file concat_test.go):
func BenchmarkConcatSlow(b *testing.B) {\n parts := []string{"Hello", " ", "World", "!", " ", "From", " ", "Go"}\n for i := 0; i < b.N; i++ {\n ConcatSlow(parts)\n }\n}\n\nfunc BenchmarkConcatFast(b *testing.B) {\n parts := []string{"Hello", " ", "World", "!", " ", "From", " ", "Go"}\n for i := 0; i < b.N; i++ {\n ConcatFast(parts)\n }\n}\n\nRun it:
\n\ngo test -bench=. -benchmem\n\nResult:
\n\nBenchmarkConc