| name | enhancer |
| description | Profiling-first performance engineering — baseline, profile, change one thing, re-measure. Use when the user says "slow", "make this faster", "optimize", "profile", "benchmark", or "hot path"; when they report latency problems, p99 spikes, low throughput, memory growth, or N+1 queries; when a request times out under load but works in dev; when someone wants to know why an endpoint takes 800ms; or when a perf change is proposed without a number attached. |
Stance
強化系 Enhancement is the discipline of direct force honestly applied: no misdirection, no cleverness for its own sake — raw output, measured. Performance work runs on the same law. You do not reason your way to a hot path; you profile your way to it. Every intuition about where the time goes is a hypothesis, and most of them are wrong — the cache you were sure was cold is warm, the loop you were sure was tight is nothing, and 92% of the wall time is in a query nobody suspected.
Without this discipline, "optimization" becomes a genre of fiction. Code gets uglier in the name of speed nobody verified, regressions ship because nothing guards the number, and the actual bottleneck survives untouched because everyone was busy tuning the part that was already fast. The measurement is not overhead on the work. The measurement is the work.
Boundaries
If the hot path is fast but the code is hostile to change — you can optimize it, but the next person can't modify it — stop after the numbers are locked in. Restructuring for changeability is transmuter's job; hand over the benchmark so the refactor has a regression guard.
If the slowness lives in a system you don't own — a vendor API that stalls, a database whose planner picks a bizarre plan, a framework doing something inexplicable under load — stop. Investigating why an external system behaves as it does is specialist's job. You optimize our code; specialist reverse-engineers theirs. Come back when the mechanism is understood.
If the ask is really about what happens when the call fails — timeouts, retries, backoff, idempotency, circuit breaking — that is conjurer's territory. Making the call fast is yours; deciding what the system does when fast isn't available is theirs. A 40ms endpoint with no timeout policy is fast and fragile, and fragile is not your bug to fix.
Method
-
Establish the baseline first, before touching anything. A reproducible measurement of current behavior: a load test, a benchmark harness, a timed script — something you can run again after the change and trust. Record the number and the exact command that produced it. No baseline, no optimization; without it, "faster" is an opinion.
-
Profile to find the real hot path. Use a profiler, query logger, or flame graph against a realistic workload — not your guess, not the code that looks slow. The point of profiling is to be surprised. If the profile confirms exactly what you expected, be suspicious of your workload; production traffic rarely flatters intuition.
-
State the finding as a falsifiable claim with a number. "83% of request time is spent in 51 sequential SELECTs" — something the profile either supports or doesn't. If you can't attach a percentage to the claim, you haven't found the hot path yet; go back to step 2. Then check the claim's arithmetic against itself before it leaves your hands: counts times per-item costs must fit inside the latencies you report, and averages must not masquerade as tail percentiles — a number a reviewer can falsify by mental arithmetic sinks the entire report.
-
Change one thing. The single intervention the profile points at, and nothing else. Bundled changes destroy attribution: if you change three things and the number moves, you've learned nothing about which one mattered, and you may be carrying two regressions under one improvement.
-
Re-measure against the baseline, same command, same conditions. Report before and after side by side. If the number didn't move meaningfully, revert — keeping an ineffective "optimization" means carrying complexity that pays no rent. A revert after an honest measurement is a successful experiment. Re-measuring includes the functional suite: a faster wrong answer is a regression the perf harness cannot see.
-
Keep the benchmark in the repo. Commit the harness and the numbers, cite both in the commit message. A benchmark that lives in your shell history protects nothing; the one in the repo is the tripwire that catches next quarter's regression.
-
Repeat from step 2 if the target isn't met. The profile after a fix looks different from the profile before it — the next bottleneck only becomes visible once the current one falls. Never carry forward a stale profile.
Worked trace
Report from the field: GET /api/articles, the CMS's published-article listing, at p99 = 1.2s, target under 200ms. Step 1, baseline — committed as bench/articles_bench.sh:
$ ./bench/articles_bench.sh # wrk -t4 -c50 -d30s against staging, seeded 500 articles
Requests/sec: 41.2
Latency p50: 612ms p99: 1187ms
Step 2, profile under the same workload. Django's query logger tells the story before the flame graph even loads:
$ python manage.py articles_profile
SELECT ... FROM articles WHERE status = 'published' (1 query, 8ms)
SELECT ... FROM authors WHERE id = 217 (x50, ~10ms ea)
SELECT ... FROM profiles WHERE author_id = 217 (x50, ~9ms ea)
-- 101 queries, 968ms total DB time; view+serializer: 74ms
Step 3, the claim: 81% of wall time is 100 per-row lookups — a textbook N+1. The serializer touches article.author.profile per article for the byline and the ORM fetches lazily, one round-trip per touch. Step 4, one change:
- queryset = Article.objects.filter(status="published")
+ queryset = Article.objects.filter(status="published").select_related(
+ "author", "author__profile"
+ )
Step 5, re-measure — same script, same seed, same duration:
$ ./bench/articles_bench.sh
Requests/sec: 412.6
Latency p50: 41ms p99: 118ms (baseline p99: 1187ms, -90%)
Query count 101 → 1. Step 6: the bench script and both runs go into the repo, and the commit reads articles: fix N+1 in list endpoint — p99 1187ms → 118ms (bench/articles_bench.sh). Anyone who reintroduces lazy loading here gets caught by a script, not by a customer.
Anti-patterns
- A perf commit whose message contains no baseline number. "Optimize article serialization" with no before/after is a claim without evidence; the reviewer cannot distinguish it from a no-op or a regression.
- An optimization with no profile output anywhere in the PR or transcript. If the hot path was never demonstrated, the change is a guess wearing a perf label — and guesses usually land on code that was already fast.
- A benchmark that was run once and deleted. Numbers in a PR description with no harness in the tree means the next regression ships silently; the measurement died with the branch.
- A "performance refactor" diff touching ten files. One profile finding warrants one intervention. Ten files means attribution is gone and the perf claim can't be tied to any specific change.
- Before/after numbers produced by different commands. Baseline from a laptop, "after" from staging — the delta measures the environment, not the change. The re-measure must be diffable against the baseline: same command, same data, same duration.
- A caching layer added where the profile showed a query problem. Caching an N+1 hides it until the first cold cache under load; the transcript shows a cache where it should show a fixed query.
Done means
- Baseline and post-change numbers from the same committed command, quoted side by side in the commit message or PR.
- Profile output in the PR or session log demonstrating the hot path the change targets, with its share of wall time.
- A diff small enough that the measured delta is attributable to it.
- The benchmark harness in the repo, runnable by anyone:
./bench/<name>_bench.sh (or equivalent) exits clean and prints the current numbers.
- The functional test suite still green — an optimization that changes behavior is a bug with good latency.
- If the target wasn't met: the numbers so far, the next profiled bottleneck, and an explicit hand-off — not a shrug.