Als je Go-applicatie trager is dan verwacht of teveel geheugen gebruikt, is profiling met pprof de eerste stap naar een oplossing. Raden wat traag is leidt bijna altijd tot verkeerde optimalisaties, pprof laat je precies zien waar je programma zijn tijd en geheugen aan besteedt.
In dit artikel leer je hoe pprof werkt, welke profiles je kunt verzamelen, hoe je ze interpreteert met flame graphs en hoe je deze kennis inzet om echte bottlenecks op te lossen. We bouwen voort op onze gids over performance tuning en duiken nu dieper in de tool zelf.
Wat is pprof?
pprof is een profiling tool die onderdeel is van de Go standard library. Het verzamelt samples van je draaiende programma en geeft je een gedetailleerd beeld van resource-gebruik.
Er zijn twee manieren om pprof te gebruiken:
runtime/pprof, voor het schrijven van profiles naar bestanden, ideaal voor benchmarks en CLI-tools.net/http/pprof, voor het blootstellen van profiles via HTTP, perfect voor servers en langlopende processen.
De tool kan verschillende soorten profiles verzamelen: CPU, heap (memory), goroutines, blocking, mutex contention en trace events. Elk type geeft antwoord op een andere vraag.
pprof activeren in je applicatie
De snelste manier om pprof aan te zetten in een webserver is de net/http/pprof package met een underscore importeren:
package main
import (
"log"
"net/http"
_ "net/http/pprof"
)
func main() {
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
// Je echte applicatie hier
startApp()
}
De underscore zorgt ervoor dat alleen de init() van het package draait. Die registreert de pprof-handlers op de default HTTP mux onder /debug/pprof/.
Belangrijk: bind de pprof-server altijd aan localhost of een interne poort. Publiek toegankelijke pprof-endpoints zijn een security-risico omdat ze detailinformatie over je applicatie kunnen lekken.
Voor CLI-tools of benchmarks gebruik je runtime/pprof direct. Dat werkt goed samen met het Go testing pakket voor gestructureerde metingen.
CPU profiling: waar gaat de tijd heen?
CPU profiling is vaak de eerste stap als je applicatie traag aanvoelt. pprof sampled 100 keer per seconde de call stack van alle goroutines en berekent waar de meeste tijd wordt besteed.
Een CPU profile van 30 seconden ophalen:
go tool pprof http://localhost:6060/debug/pprof/profile?seconds=30
Je komt nu in een interactieve shell. Handige commando's:
top, toont de top 10 functies met de meeste eigen CPU-tijdtop -cum, sorteert op cumulatieve tijd inclusief childrenlist FunctionName, toont de broncode met timing per regelweb, opent een SVG call graph in je browser
(pprof) top
Showing nodes accounting for 28.91s, 96.37% of 30s total
flat flat% sum% cum cum%
12.34s 41.13% 41.13% 15.22s 50.73% encoding/json.Marshal
6.78s 22.60% 63.73% 6.78s 22.60% runtime.memmove
4.56s 15.20% 78.93% 4.89s 16.30% myapp/handlers.processOrder
De flat kolom is tijd in de functie zelf, cum is inclusief aangeroepen functies. Als encoding/json.Marshal bovenaan staat, weet je waar je moet beginnen.
Memory profiling: wie alloceert wat?
Memory profiling werkt anders dan CPU profiling. In plaats van tijd te samplen, registreert pprof allocaties. Je kunt twee dingen meten:
inuse_space, geheugen dat nu nog leeft (default)alloc_space, totaal gealloceerd geheugen over de levensduur
Voor het vinden van memory leaks gebruik je inuse_space. Voor het verminderen van GC-druk kijk je naar alloc_space en alloc_objects.
go tool pprof http://localhost:6060/debug/pprof/heap
# Voor allocatie-hotspots
go tool pprof -alloc_space http://localhost:6060/debug/pprof/heap
Een veelvoorkomende vondst zijn onnodige allocaties in hot paths: strings die elke request opnieuw worden gebouwd, slices die niet worden hergebruikt, of []byte naar string conversies in loops. Als je dit leest na onze post over memory management en de garbage collector, herken je deze patronen snel.
Flame graphs: profiles visualiseren
Tekstoutput is handig voor snel zoeken, maar voor complexe applicaties wil je een flame graph. Dat is een visualisatie waarin de breedte van elk blok aangeeft hoeveel tijd of geheugen die functie gebruikt.
Vanaf Go 1.11 heeft pprof een ingebouwde webinterface:
go tool pprof -http=:8080 http://localhost:6060/debug/pprof/profile?seconds=30
Open vervolgens http://localhost:8080 in je browser. Je krijgt verschillende views: Top, Graph, Flame Graph, Peek en Source. De Flame Graph laat je in één oogopslag zien welke code paths het meeste tijd kosten.
Tips voor het lezen van flame graphs:
- Breed is slecht, smal is goed, brede blokken bovenin zijn directe hotspots
- Plateaus duiden op functies die veel zelf doen
- Hoge stacks zijn niet per se een probleem als ze smal zijn
- Zoek naar verrassingen: dingen die je niet had verwacht bovenaan
Voor meer achtergrond over flame graphs is Brendan Gregg's originele artikel een klassieker.
Goroutine profiling: concurrency debuggen
Als je applicatie goroutines lekt of blijft hangen, is de goroutine profile onmisbaar. Die toont hoeveel goroutines actief zijn en waar ze staan:
curl http://localhost:6060/debug/pprof/goroutine?debug=2 > goroutines.txt
Met debug=2 krijg je een tekstueel rapport met elke goroutine, stack trace en status. Zie je duizenden goroutines die allemaal in dezelfde channel-read hangen? Dan heb je een leak.
Dit profile is vooral nuttig als je werkt met worker pools en pipelines of complexe channel-structuren zoals we besproken in channels diep uitgelegd.
Block en mutex profiling
Deze twee profiles staan default uit omdat ze meer overhead hebben. Je zet ze expliciet aan:
import "runtime"
func main() {
runtime.SetBlockProfileRate(1) // Alle blocking events
runtime.SetMutexProfileFraction(1) // Alle mutex contention
// ...
}
- Block profile, laat zien waar goroutines blokkeren op channels, selects of system calls
- Mutex profile, toont contention op
sync.Mutexensync.RWMutex
Als je onder load je throughput niet omhoog krijgt terwijl je CPU niet op 100% zit, is er vaak contention. Deze profiles helpen je precies te vinden waar.
Profiling met benchmarks
Een krachtige combinatie is pprof samen met Go benchmarks. Zo profile je geïsoleerde code:
go test -bench=. -cpuprofile=cpu.out -memprofile=mem.out
go tool pprof cpu.out
Dit is de beste workflow voor het optimaliseren van specifieke functies. Je schrijft een benchmark, profileert die, past de code aan en meet opnieuw. Herhaal tot de hotspot verdwijnt.
func BenchmarkProcessOrders(b *testing.B) {
orders := generateOrders(1000)
b.ResetTimer()
for i := 0; i < b.N; i++ {
processOrders(orders)
}
}
De -benchmem flag voegt memory-statistieken toe zodat je allocaties per operatie ziet.
Continuous profiling in productie
Moderne Go-teams draaien pprof niet alleen lokaal. Tools zoals Pyroscope of Google Cloud Profiler verzamelen continu kleine profiles uit productie. Zo zie je trends over tijd en vind je regressies meteen.
Dit past goed in een bredere observability-strategie. Zie onze post over logging en observability voor hoe je dit combineert met metrics en tracing.
De overhead van continuous profiling is laag (typisch 1-3% CPU) maar de inzichten zijn enorm. Je hoeft niet meer te gokken of een performance-probleem door code of door load komt, je ziet het direct in de profile.
Veelgemaakte fouten bij profiling
Bij het werken met pprof zie ik een paar patronen regelmatig fout gaan:
Profile in ontwikkelingsomgeving, locale hardware is anders dan productie. Profile altijd in een omgeving die lijkt op productie, met realistische load.
Te korte profiles, een CPU profile van 5 seconden geeft ruis. Gebruik minimaal 30 seconden onder echte belasting.
Optimaliseren zonder meten, eerst profilen, dan optimaliseren, dan opnieuw profilen. Zonder baseline weet je niet of je verbetering echt werkt.
Pprof blootstellen aan internet, zet /debug/pprof/ nooit op een publieke URL. Gebruik een interne poort, een reverse proxy met auth, of schakel het alleen tijdelijk in.
Alleen CPU profilen, memory en goroutine profiles onthullen vaak problemen die CPU profiles missen. Bij trage response tijden zonder hoge CPU kijk je naar blocking of mutex.
Van profile naar oplossing
Een profile is geen eindpunt maar een startpunt. Als je een hotspot vindt, overweeg deze opties:
- Algoritmisch, kun je de complexiteit verlagen? Van O(n²) naar O(n log n) helpt altijd meer dan micro-optimalisatie.
- Caching, bereken je hetzelfde resultaat opnieuw? Een
sync.Mapof LRU-cache kan veel schelen. - Pooling, alloceer je telkens dezelfde objecten? Gebruik
sync.Poolom ze te hergebruiken. - Parallellisme, is het werk parallelliseerbaar? Een worker pool kan CPU-bound werk versnellen.
- I/O optimaliseren, database queries, disk-access en network calls zijn vaak de echte boosdoeners.
De officiële Go blog post over profiling uit 2011 is nog steeds een excellente introductie met concrete optimalisatie-voorbeelden.
Veelgestelde vragen
Wat is pprof in Go?
pprof is een ingebouwde profiling tool in Go waarmee je CPU-gebruik, geheugenallocaties, goroutines en blocking kunt analyseren. Het is onderdeel van de standard library via runtime/pprof en net/http/pprof.
Wat is het verschil tussen CPU en memory profiling?
CPU profiling laat zien waar je programma zijn tijd aan besteedt door de call stack te samplen. Memory profiling toont welke functies het meeste geheugen alloceren. Beide gebruiken pprof maar richten zich op verschillende bottlenecks.
Is pprof veilig om in productie te gebruiken?
Ja, pprof heeft een lage overhead (meestal onder 5%) en wordt standaard door veel Go-teams in productie ingezet. Zorg wel dat de pprof-endpoints niet publiek toegankelijk zijn, want ze kunnen gevoelige informatie lekken.
Wat is een flame graph?
Een flame graph is een visualisatie van profiling data waarbij de x-as functies toont en de y-as de call stack. Hoe breder een blok, hoe meer tijd die functie gebruikt. Het is de snelste manier om bottlenecks te spotten.
Hoe start ik met pprof in een bestaande applicatie?
Importeer net/http/pprof met een underscore en start een HTTP-server op een interne poort. Haal daarna profiles op met go tool pprof en het endpoint. Je hebt binnen enkele minuten inzicht in performance.
Afsluitend
Profiling met pprof is een van de krachtigste vaardigheden voor Go-developers die serieus met performance bezig zijn. De tool is gratis, ingebouwd en levert concrete inzichten op in plaats van gissingen.
Begin klein: voeg net/http/pprof toe aan één applicatie, verzamel een CPU profile onder load en kijk wat bovenaan staat. Je zult verrast zijn hoe vaak de bottleneck ergens zit waar je het niet verwacht had.