Skip to content

Profile Before Optimizing

Claude CodeSKILL.mdCoding

A measurement-first optimization loop — profile under realistic load, fix exactly one hot spot, re-measure, and keep the numbers in the commit.

Use when: Use when something is slow or resource-hungry and the urge is to start rewriting code that merely looks expensive.

@library0 upvotes0 remixes0 comments

Get this skill

Add the skill file with the CLI, by hand, or over MCP.

Save this to your project or home directory so Claude Code can load it.

npx goodprompts add performance-profiling-before-optimizing
Saves to~/.claude/skills/performance-profiling-before-optimizing/SKILL.md
npx goodprompts add performance-profiling-before-optimizing
Saves to ~/.claude/skills/performance-profiling-before-optimizing/SKILL.md

Skill file

SKILL.md3,164 characters
# Profile Before Optimizing
Optimizations without measurements are guesses, and most guesses about hot paths are wrong. No code change until a profile names the culprit; no merged change without a before/after number.
## Step 1 — Define the metric and budget
- Pick one number: p95 request latency, wall-clock of a batch job, peak RSS, queries per request. "Feels slow" is not a metric.
- Record the current value and the target ("p95 from 1.8s to <500ms"). The target tells you when to stop — open-ended optimization never ends.
## Step 2 — Measure under realistic conditions
- Profile with production-like data volume. Code that's fine at 100 rows is quadratic at 100k; a dev DB with 50 records hides every N+1.
- Use the right tool for the layer:
- CPU: a sampling profiler with flame graph output (`py-spy`, `node --cpu-prof` + speedscope, `perf`, pprof).
- Database: query logs with timings, `EXPLAIN ANALYZE` on the slow queries, a counter for queries-per-request.
- Memory: heap snapshots diffed across the operation; allocation profilers for churn.
- Frontend: browser performance panel, Web Vitals — measured on a throttled profile, not your dev machine.
- Run the measurement at least 3 times after a warm-up run; record median and spread. A single timing is noise.
## Step 3 — Read the profile, name one culprit
- Walk the flame graph top-down by inclusive time. Ignore anything under ~5% of total — it cannot give you a meaningful win even if reduced to zero.
- Distinguish on-CPU from waiting. If the process is idle while the wall clock runs, the fix is I/O-shaped (batching, caching, parallelism, indexes), not algorithmic.
- Classic culprits to check before exotic theories: N+1 queries, missing index (seq scan in EXPLAIN), serialization of large payloads, synchronous calls in a loop that could be batched, accidental O(n²) via `includes`/`in` inside a loop, re-reading or re-parsing the same file/config per item.
- Write the diagnosis down: "62% of wall time is 1,400 sequential SELECTs from rendering badges per row."
## Step 4 — Fix one thing
- Apply the single change that addresses the named culprit. Prefer, in order: do less work (cache, skip, precompute) → do it in bulk (batch query, vectorize) → do it concurrently → micro-optimize. Micro-optimizations come last because they cost readability for single-digit percents.
- Do not bundle a second "while I'm in here" optimization. One change per measurement, or you can't attribute the result.
## Step 5 — Re-measure and decide
- Re-run the exact Step 2 measurement. Compare medians.
- Improvement below the noise floor → revert. Keep no complexity that isn't paid for by a measured win.
- Target met → stop, even if you can see more opportunities. File them instead.
- Target not met → back to Step 3 with a fresh profile; the hot path has moved.
## Commit etiquette
- The commit/PR body includes: metric, before, after, measurement method, and a link to or summary of the profile. "perf: batch badge query (p95 1.8s → 410ms, 1400 queries → 3)".
- Add a regression guard where feasible: a query-count assertion in a test, or a benchmark in CI for the critical path.
Comments

Comments

Sign in to commentSign in

Remixes

Loading remixes…