Performance Profiler
Overview
Make slow things fast — correctly and provably. This skill enforces a
measure-driven loop: never optimize on a hunch, always profile to find the real
hot path, fix the biggest contributor first, then re-measure to confirm the win
and guard against regressions.
Keywords: performance, profiling, optimization, bottleneck, latency, throughput,
p99, slow, benchmark, flamegraph, CPU profile, memory leak, allocations, N+1
query, caching, big-O, complexity, hot path, regression.
The cardinal rule: measure first. Most "obvious" optimizations target the
wrong code. Profilers routinely show that 90% of time sits in a place nobody
suspected.
Workflow
Follow this loop. Do not skip steps — especially step 1 and step 6.
Define the goal and a metric. Pick ONE primary metric and a target:
wall-clock latency (p50/p95/p99), throughput (req/s, rows/s), CPU time,
peak memory (RSS), or allocations. Write down the current value and the
target. "Make it faster" is not a goal; "cut p95 from 800ms to under 200ms"
is.
Reproduce reliably. Build a repeatable scenario with representative data
volume. A bottleneck at 10 rows may vanish at 10M and vice-versa. Disable
noise: warm caches, JIT warmup, fixed input, quiet machine, multiple runs.
Measure the baseline. Time/benchmark the whole operation before touching
anything. Save the numbers. Use scripts/bench.py for a quick statistically
sane wall-clock benchmark of a Python callable or shell command.
Profile to find the hot path. Use a real profiler (not scattered print
timers) to attribute cost. Find the function/line/query consuming the most
time or memory. See references/profiling-tools.md for the right tool per
language and how to read its output.
Diagnose and fix the top contributor. Apply the cheapest effective fix
from the optimization hierarchy (see below). Change ONE thing at a time so
each change's impact is attributable.
Re-measure and verify. Re-run the exact baseline benchmark. Confirm the
metric improved and correctness is unchanged. Quantify: "p95 800ms → 180ms,
-77%". If no improvement, revert and re-profile.
Repeat or stop. Return to step 4 for the next hot path, or stop when the
target is met. Add a regression guard (a benchmark assertion or CI check) so
the win doesn't rot.
Optimization hierarchy
Apply fixes in this order — cheapest/highest-leverage first. Most wins come from
the top three.
- Do less work. Remove redundant computation, dead code, needless copies,
logging in hot loops. Cache or memoize pure, repeated results. Hoist
invariants out of loops. Compute lazily / short-circuit.
- Reduce algorithmic complexity. Replace O(n²) with O(n log n) or O(n):
use a hash set/map for membership and lookups, sort once instead of
repeatedly scanning, use the right data structure. See
references/complexity-cheatsheet.md.
- Fix I/O and data access. Batch round-trips, eliminate N+1 queries, add
the right index, select only needed columns, stream instead of buffering,
use connection pooling, paginate. I/O usually dwarfs CPU.
- Parallelize / concurrency. Overlap independent I/O (async), use a worker
pool for CPU-bound work, vectorize (NumPy/SIMD). Only after single-thread
work is minimized — parallelizing a bad algorithm just burns more cores.
- Reduce allocations / memory pressure. Reuse buffers, avoid intermediate
collections, use generators/iterators, pick compact representations, cut GC
churn.
- Lower-level / micro-optimizations. Compiled extensions, better serializer,
tuned runtime flags, JIT-friendly code. Last resort — small payoff, high
maintenance cost.
Decision framework
- CPU-bound vs I/O-bound first. If wall time >> CPU time, you are waiting on
I/O (disk, network, DB, locks) — chase step 3/4, not micro-CPU tuning. If wall
time ≈ CPU time, attack the algorithm (step 1/2).
- Latency vs throughput. Caching and batching help throughput; they may not
help a single cold request's latency. Optimize for the metric you committed to.
- Tail vs average. p99 spikes are usually GC pauses, lock contention, cold
caches, or a slow dependency — not the average path. Profile the slow requests
specifically.
- Amdahl's law. Speeding a section that's 5% of runtime can yield at most a
5% gain. Always spend effort proportional to a section's share of total cost.
Best Practices
- Profile in a configuration that resembles production (data size, build flags,
release/optimized mode — never profile a debug build and extrapolate).
- Change one variable per measurement; keep a running log of (change → metric).
- Run benchmarks multiple times; report median and spread, not a single run.
- Keep correctness tests green throughout — a fast wrong answer is worthless.
- Commit the baseline numbers and the proof of improvement alongside the change.
- Prefer eliminating work over doing the same work faster.
- Add a regression benchmark to CI for anything you fought hard to speed up.
Common Pitfalls
- Optimizing without profiling — fixing code that isn't the bottleneck.
- Premature optimization — complicating code for gains the user never feels.
- Micro-benchmark lies — measuring a case the optimizer elides, or one with
unrepresentative data/caching, then shipping a non-win.
- Benchmarking debug builds or with profiler overhead included.
- Single-run conclusions — noise mistaken for signal.
- Premature parallelism — adding threads/async over an O(n²) core.
- Cache without invalidation strategy — turns a speed bug into a correctness bug.
- Ignoring the tail — celebrating a better mean while p99 still times out.
Supporting files
references/profiling-tools.md — per-language profiler commands (Python,
Node/JS, Go, Java, SQL, web/browser), what each measures, and how to read
flamegraphs and call trees.
references/complexity-cheatsheet.md — big-O of common operations and data
structures, plus the canonical "swap this for that" optimization patterns
(N+1 fix, set-membership, memoization, batching).
scripts/bench.py — runnable benchmark harness: times a Python expression or
a shell command over N runs and reports min/median/mean/stdev with a clean
comparison mode for before/after.
examples/optimize-n-plus-one.md — full worked example taking a slow endpoint
from 1.9s to 60ms through the whole loop (measure → profile → fix → verify).
1---2name: performance-profiler3description: Systematically finds and fixes performance bottlenecks by measuring first, profiling hot paths, reducing algorithmic and I/O cost, and verifying gains with before/after benchmarks. Use this skill when code, an endpoint, a page, a query, or a job is "slow", "laggy", "timing out", "using too much CPU/memory", or "doesn't scale"; when asked to optimize, speed up, profile, or benchmark something; when chasing high latency / low throughput, p99 spikes, memory leaks, N+1 queries, or excessive allocations; or before/after a perf-sensitive change to prove it actually helped. Covers Python, Node/JS, Go, Java, SQL, and HTTP/web frontends.4license: MIT5---67# Performance Profiler89## Overview1011Make slow things fast — correctly and provably. This skill enforces a12measure-driven loop: never optimize on a hunch, always profile to find the real13hot path, fix the biggest contributor first, then re-measure to confirm the win14and guard against regressions.1516Keywords: performance, profiling, optimization, bottleneck, latency, throughput,17p99, slow, benchmark, flamegraph, CPU profile, memory leak, allocations, N+118query, caching, big-O, complexity, hot path, regression.1920The cardinal rule: **measure first**. Most "obvious" optimizations target the21wrong code. Profilers routinely show that 90% of time sits in a place nobody22suspected.2324## Workflow2526Follow this loop. Do not skip steps — especially step 1 and step 6.27281. **Define the goal and a metric.** Pick ONE primary metric and a target:29 wall-clock latency (p50/p95/p99), throughput (req/s, rows/s), CPU time,30 peak memory (RSS), or allocations. Write down the current value and the31 target. "Make it faster" is not a goal; "cut p95 from 800ms to under 200ms"32 is.33342. **Reproduce reliably.** Build a repeatable scenario with representative data35 volume. A bottleneck at 10 rows may vanish at 10M and vice-versa. Disable36 noise: warm caches, JIT warmup, fixed input, quiet machine, multiple runs.37383. **Measure the baseline.** Time/benchmark the whole operation before touching39 anything. Save the numbers. Use `scripts/bench.py` for a quick statistically40 sane wall-clock benchmark of a Python callable or shell command.41424. **Profile to find the hot path.** Use a real profiler (not scattered print43 timers) to attribute cost. Find the function/line/query consuming the most44 time or memory. See `references/profiling-tools.md` for the right tool per45 language and how to read its output.46475. **Diagnose and fix the top contributor.** Apply the cheapest effective fix48 from the optimization hierarchy (see below). Change ONE thing at a time so49 each change's impact is attributable.50516. **Re-measure and verify.** Re-run the exact baseline benchmark. Confirm the52 metric improved and correctness is unchanged. Quantify: "p95 800ms → 180ms,53 -77%". If no improvement, revert and re-profile.54557. **Repeat or stop.** Return to step 4 for the next hot path, or stop when the56 target is met. Add a regression guard (a benchmark assertion or CI check) so57 the win doesn't rot.5859## Optimization hierarchy6061Apply fixes in this order — cheapest/highest-leverage first. Most wins come from62the top three.63641. **Do less work.** Remove redundant computation, dead code, needless copies,65 logging in hot loops. Cache or memoize pure, repeated results. Hoist66 invariants out of loops. Compute lazily / short-circuit.672. **Reduce algorithmic complexity.** Replace O(n²) with O(n log n) or O(n):68 use a hash set/map for membership and lookups, sort once instead of69 repeatedly scanning, use the right data structure. See70 `references/complexity-cheatsheet.md`.713. **Fix I/O and data access.** Batch round-trips, eliminate N+1 queries, add72 the right index, select only needed columns, stream instead of buffering,73 use connection pooling, paginate. I/O usually dwarfs CPU.744. **Parallelize / concurrency.** Overlap independent I/O (async), use a worker75 pool for CPU-bound work, vectorize (NumPy/SIMD). Only after single-thread76 work is minimized — parallelizing a bad algorithm just burns more cores.775. **Reduce allocations / memory pressure.** Reuse buffers, avoid intermediate78 collections, use generators/iterators, pick compact representations, cut GC79 churn.806. **Lower-level / micro-optimizations.** Compiled extensions, better serializer,81 tuned runtime flags, JIT-friendly code. Last resort — small payoff, high82 maintenance cost.8384## Decision framework8586- **CPU-bound vs I/O-bound first.** If wall time >> CPU time, you are waiting on87 I/O (disk, network, DB, locks) — chase step 3/4, not micro-CPU tuning. If wall88 time ≈ CPU time, attack the algorithm (step 1/2).89- **Latency vs throughput.** Caching and batching help throughput; they may not90 help a single cold request's latency. Optimize for the metric you committed to.91- **Tail vs average.** p99 spikes are usually GC pauses, lock contention, cold92 caches, or a slow dependency — not the average path. Profile the slow requests93 specifically.94- **Amdahl's law.** Speeding a section that's 5% of runtime can yield at most a95 5% gain. Always spend effort proportional to a section's share of total cost.9697## Best Practices9899- Profile in a configuration that resembles production (data size, build flags,100 release/optimized mode — never profile a debug build and extrapolate).101- Change one variable per measurement; keep a running log of (change → metric).102- Run benchmarks multiple times; report median and spread, not a single run.103- Keep correctness tests green throughout — a fast wrong answer is worthless.104- Commit the baseline numbers and the proof of improvement alongside the change.105- Prefer eliminating work over doing the same work faster.106- Add a regression benchmark to CI for anything you fought hard to speed up.107108## Common Pitfalls109110- **Optimizing without profiling** — fixing code that isn't the bottleneck.111- **Premature optimization** — complicating code for gains the user never feels.112- **Micro-benchmark lies** — measuring a case the optimizer elides, or one with113 unrepresentative data/caching, then shipping a non-win.114- **Benchmarking debug builds** or with profiler overhead included.115- **Single-run conclusions** — noise mistaken for signal.116- **Premature parallelism** — adding threads/async over an O(n²) core.117- **Cache without invalidation strategy** — turns a speed bug into a correctness bug.118- **Ignoring the tail** — celebrating a better mean while p99 still times out.119120## Supporting files121122- `references/profiling-tools.md` — per-language profiler commands (Python,123 Node/JS, Go, Java, SQL, web/browser), what each measures, and how to read124 flamegraphs and call trees.125- `references/complexity-cheatsheet.md` — big-O of common operations and data126 structures, plus the canonical "swap this for that" optimization patterns127 (N+1 fix, set-membership, memoization, batching).128- `scripts/bench.py` — runnable benchmark harness: times a Python expression or129 a shell command over N runs and reports min/median/mean/stdev with a clean130 comparison mode for before/after.131- `examples/optimize-n-plus-one.md` — full worked example taking a slow endpoint132 from 1.9s to 60ms through the whole loop (measure → profile → fix → verify).