| name | performance-profiling |
| description | Systematic performance profiling and optimization methodology. Covers CPU profiling, memory profiling, heap analysis, goroutine/thread analysis, FlameGraph generation, latency profiling, database query profiling, network I/O profiling, and continuous performance regression detection.
USE WHEN: investigating performance issues, optimizing slow code, reducing memory usage, diagnosing GC/CPU bottlenecks, finding N+1 queries, analyzing pprof/flamegraph output, or establishing a performance baseline. Triggers on "profiling", "performance analysis", "slow code", "flame graph", "pprof", "heap dump", "memory leak", "CPU spike", "bottleneck".
|
Performance Profiling
Source: Google performance engineering + pprof/cpuprofile/flamegraph
practical experience + real-world debugging at Google Brain/ByteDance
Core Philosophy: "Don't optimize what you can't measure. Don't measure
what you can't understand."
Core Principles
Performance optimization is NOT:
โฆ "I think this might be slow" โ guessing
โฆ "Let's micro-optimize this function" โ premature
โฆ "Rewrite in Rust/Go" โ last resort
Performance optimization IS:
โ Instrument first โ profile second โ fix third
โ Optimizing the right 10% (Pareto principle)
โ Measuring before AND after every change
โ Understanding the system, not just the code
1. The Profiling Workflow
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Step 1: DEFINE THE PROBLEM โ
โ "The request takes 500ms, it should take < 100ms" โ
โ 1a. Check monitoring: where is the time spent? โ
โ 1b. Hypothesis: "Time is spent in database queries" โ
โ โ
โ Step 2: PROFILE (measure current state) โ
โ 2a. Collect CPU profile (pprof, perf) โ
โ 2b. Collect memory/heap profile โ
โ 2c. Collect latency breakdown (tracing) โ
โ โ
โ Step 3: ANALYZE (find the bottleneck) โ
โ 3a. Generate flame graph โ
โ 3b. Identify hottest paths โ
โ 3c. Confirm hypothesis or revise โ
โ โ
โ Step 4: FIX (targeted optimization) โ
โ 4a. Apply minimal, targeted fix โ
โ 4b. NEVER guess โ fix only what the profile reveals โ
โ โ
โ Step 5: VERIFY (measure again) โ
โ 5a. Re-run profile โ
โ 5b. Compare before/after metrics โ
โ 5c. If not fixed โ return to Step 2 โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
2. CPU Profiling
2.1 Go (pprof)
import (
"net/http"
_ "net/http/pprof"
)
func main() {
go func() {
log.Println(http.ListenAndServe("localhost:6060", nil))
}()
http.HandleFunc("/", handler)
log.Fatal(http.ListenAndServe(":8080", nil))
}
curl -o cpu.pprof 'http://localhost:6060/debug/pprof/profile?seconds=30'
go tool pprof cpu.pprof
(pprof) top 20
(pprof) web
(pprof) list handleOrder
git clone https://github.com/brendangregg/FlameGraph
go tool pprof -raw cpu.pprof > cpu_raw.txt
./FlameGraph/stackcollapse-go.pl cpu_raw.txt > cpu_collapsed.txt
./FlameGraph/flamegraph.pl cpu_collapsed.txt > cpu_flame.svg
pprof -http=:8081 cpu.pprof
2.2 Node.js
import * as profiler from 'v8-profiler-next';
profiler.startProfiling('cpu-profile');
await doWork();
const profile = profiler.stopProfiling('cpu-profile');
profile.export((err, result) => {
fs.writeFileSync('cpu-profile.cpuprofile', result);
profile.delete();
});
node --inspect-brk app.js
npx clinic doctor -- node app.js
npx clinic flame -- node app.js
2.3 Python (cProfile)
python -m cProfile -o profile.cprof myscript.py
import cProfile
profiler = cProfile.Profile()
profiler.enable()
result = process_orders()
profiler.disable()
profiler.dump_stats('profile.cprof')
python -m snakeviz profile.cprof
pip install py-spy
py-spy record -o python_flame.svg -- python myscript.py
py-spy top --pid 12345
py-spy record -o profile.svg --pid 12345
3. Memory Profiling
3.1 Go (Heap Profiling)
curl -o heap.pprof 'http://localhost:6060/debug/pprof/heap'
go tool pprof -http=:8082 heap.pprof
go tool pprof -base heap_before.pprof heap_after.pprof
go tool pprof -inuse_objects heap.pprof
go tool pprof -alloc_objects heap.pprof
go tool pprof -inuse_space heap.pprof
go tool pprof -inuse_objects heap.pprof
3.2 Node.js
const heapdump = require('heapdump');
process.on('SIGUSR2', () => {
heapdump.writeSnapshot(`/tmp/heap-${Date.now()}.heapsnapshot`);
});
setInterval(() => {
heapdump.writeSnapshot(`/tmp/heap-${Date.now()}.heapsnapshot`);
}, 60000);
node --expose-gc app.js
node --trace-gc app.js 2>&1 | grep -E 'Mark-sweep|Scavenge|IncrementalMark'
3.3 Python
import tracemalloc
tracemalloc.start()
snapshot = tracemalloc.take_snapshot()
stats = snapshot.statistics('lineno')
for stat in stats[:10]:
print(stat)
pip install memory_profiler
python -m memory_profiler myscript.py
4. Latency Profiling (Tracing)
4.1 Distributed Tracing
const opentelemetry = require('@opentelemetry/api');
const tracer = opentelemetry.trace.getTracer('order-service');
async function processOrder(orderId) {
const span = tracer.startSpan('processOrder');
span.setAttribute('order.id', orderId);
try {
const order = await db.getOrder(orderId);
const priced = await pricingService.calculate(order);
return priced;
} finally {
span.end();
}
}
4.2 Database Query Profiling
SET log_min_duration_statement = 100;
SELECT pid, now() - pg_stat_activity.query_start AS duration,
query, state
FROM pg_stat_activity
WHERE state != 'idle'
ORDER BY duration DESC;
EXPLAIN ANALYZE
SELECT * FROM orders
WHERE customer_id = 'c001'
AND created_at > '2026-01-01';
4.3 N+1 Query Detection
from django.db import connection
from django.test.utils import CaptureQueriesContext
with CaptureQueriesContext(connection) as ctx:
result = get_orders_with_items()
num_queries = len(ctx.captured_queries)
import { getConnection } from 'typeorm';
const queryCount = 0;
getConnection().driver.postgres.on('query', () => queryCount++);
5. FlameGraph Interpretation
5.1 Reading a Flame Graph
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ โ
โ X-axis: Stack profile population (width = time spent) โ
โ Y-axis: Stack depth (bottom = entry, top = leaf) โ
โ โ
โ ๐ฅ WIDE columns = HOT (spend lots of time here) โ
โ ๐ฅ TALL stacks = DEEP (many nested calls) โ
โ โ
โ What to look for: โ
โ 1. WIDEST top-level plateaus = hottest functions โ
โ 2. "Plateaus" where many tall stacks converge = โ
โ serialization points, lock contention โ
โ 3. Missing functions = time spent outside your code โ
โ (GC, kernel, I/O wait) โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
5.2 Common Patterns
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Pattern 1: "Wide Mountain" โ
โ A single wide column โ this function IS the bottleneck โ
โ Fix: optimize that function or cache its result โ
โ โ
โ Pattern 2: "Many Same-Width Peaks" โ
โ Many functions each consuming similar time โ
โ Fix: reduce the number of calls (batch, cache) โ
โ โ
โ Pattern 3: "Tall Spiky Tower" โ
โ Deep recursion โ O(nยฒ) algorithm or deep call chain โ
โ Fix: flatten the recursion, cache intermediate results โ
โ โ
โ Pattern 4: "GC/Alloc Cloud" โ
โ Large chunk of CPU in GC/malloc โ too many allocations โ
โ Fix: reduce allocations, object pooling, stack alloc โ
โ โ
โ Pattern 5: "Empty Space" โ
โ CPU profile doesn't show much โ I/O bound โ
โ Fix: measure I/O separately (disk, network, lock) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
6. Common Bottlenecks & Fixes
โโโโโโโโโโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Symptom โ Likely Cause โ Fix โ
โโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโค
โ CPU 100%, high req โ Inefficient โ Profile โ optimize โ
โ latency โ algorithm โ hot path โ
โโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Memory grows forever โ Memory leak โ Heap diff โ find โ
โ โ โ growing object graph โ
โโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Periodic latency โ GC / Full GC โ Reduce alloc rate, โ
โ spikes โ โ tune gc params โ
โโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโค
โ High DB query latencyโ Missing indexโ EXPLAIN ANALYZE โ โ
โ โ โ add index โ
โโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Many DB queries for โ N+1 problem โ Eager loading / batchingโ
โ 1 request โ โ โ
โโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Threads blocked โ Lock โ Reduce lock scope, โ
โ waiting โ contention โ use lock-free, shard โ
โโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Low network throughputโ TCP buffer โ Tune TCP params, โ
โ โ too small โ batching โ
โโโโโโโโโโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโผโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Slow after deploy โ Config โ Check config, warm โ
โ โ change / coldโ caches, pre-compile โ
โ โ start โ โ
โโโโโโโโโโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโดโโโโโโโโโโโโโโโโโโโโโโโโโ
7. Continuous Performance
7.1 Benchmarking in CI
func BenchmarkProcessOrder(b *testing.B) {
order := createTestOrder()
for i := 0; i < b.N; i++ {
processOrder(order)
}
}
go test -bench=. -benchmem -count=5 > /tmp/before.txt
go test -bench=. -benchmem -count=5 > /tmp/after.txt
go install golang.org/x/perf/cmd/benchstat@latest
benchstat /tmp/before.txt /tmp/after.txt
7.2 Performance Dashboard
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ Performance Dashboard (Grafana) โ
โ โ
โ Request Latency (p50/p95/p99) โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ โโโโโ
โโโโโโ
โโโโ p50: 45ms p95: 120ms โ โ
โ โ p99: 890ms (spike at 2pm)โ โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ โ
โ CPU Profile (Hot Functions, latest deploy) โ
โ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ โ
โ orderService.processOrder: 45% (+12% from baseline) ๐จ โ
โ db.query: 22% (-3% from baseline) โ
โ json.Marshal: 18% (new!) โ ๏ธ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
8. Golden Rules
1. ALWAYS profile before optimizing
The hottest function is never the one you think it is.
2. One change at a time
Apply one optimization โ measure โ verify โ commit.
Multiple changes at once โ you don't know what worked.
3. Measure p99, not average
Average hides the problem. p99 shows the true user experience.
4. Profile under realistic load
Empty system and loaded system have entirely different profiles.
5. Cache is not a performance strategy, it's a complexity trade-off
Adding cache means adding: invalidation logic, staleness tolerance,
operational complexity, cold-start code paths.
6. Don't optimize allocs that don't matter
A 1ms allocation reduction when p99 is 500ms โ misdirected effort.
7. The best optimization is not doing the work at all
โ Can we skip this computation?
โ Can we pre-compute and cache?
โ Can we defer and batch?
References
references/flamegraph-interpretation.md โ Detailed flame graph reading guide
references/perf-command-guide.md โ Linux perf tool comprehensive reference
references/profiling-recipes.md โ Language-specific profiling recipes