Back to blog
Engineering13 min read

Prompting for Performance Optimisation

Performance optimization prompts work when they force measurement first: a profile, a hypothesis, and a before/after benchmark, not a confident guess about what's slow.

NH
Nafiul Hasan
Founder, Prompt Architects

TL;DR: Performance optimization prompts fail when they skip straight to a fix. The reliable pattern is measure, hypothesize, change, measure again: paste real profiler numbers instead of a vibe, name one constraint (latency, throughput, or memory), forbid invented percentages, and require the exact command that will prove the change worked before you trust it.

What are performance optimization prompts, and why does "make this faster" fail?

A prompt that says "optimize this for performance" hands the model a blank check with no way to spend it well. The model cannot run your code, cannot see your production traffic, and cannot tell whether the delay your users complain about lives in this function or in a database call four layers below it. Faced with that gap, it does what anyone would do when asked to grade an exam with no answer key: it grades on style. It looks for shapes that read as slow, a loop inside a loop, a string built with repeated concatenation, a call that could plausibly be cached, and rewrites them, whether or not they cost you anything.

Change the part of a program that consumes two percent of its runtime and you have, at best, made a two-percent program disappear. That is not a subtle insight, and it predates language models by decades: it is the entire justification for profiling before touching a line of code. What changed is that a fluent model will now hand you a plausible-sounding rewrite of the two-percent part, complete with a confident explanation of why it should be faster, and nothing in that explanation tells you whether it moved the needle at all.

Why do you need a baseline before you ask for anything?

Because "optimize this" is not a falsifiable request. There is no version of the model's answer you can check against reality if you never captured what reality was in the first place. Two separate measurements do two separate jobs here, and conflating them is the single most common structural mistake in a performance prompt.

A profile answers where time or memory goes: which function, which line, what share of the total. A benchmark answers whether a change helped: the identical workload, run before and after, with the difference expressed as a real number. Python's own standard library documentation draws this line explicitly: "The profiler modules are designed to provide an execution profile for a given program, not for benchmarking purposes (for that, there is timeit for reasonably accurate results)" (Python docs, The Python Profilers, accessed 3 September 2026). Skip the profile and you are guessing at the target. Skip the benchmark and you have no way to know if you hit it.

Here is how to get a real profile from three common runtimes, using each language's own built-in tooling rather than a third-party guess:

# Python — cProfile is the C-extension profiler the docs recommend for
# most users; "profile" (pure Python) adds significant overhead of its own.
python -m cProfile -o out.prof your_script.py
python -c "import pstats; pstats.Stats('out.prof').sort_stats('cumulative').print_stats(15)"

# Node.js — the built-in V8 profiler, documented at nodejs.org/en/learn
NODE_ENV=production node --prof app.js
node --prof-process isolate-0x*-v8.log > processed.txt

# Go — CPU profiling via the standard library's runtime/pprof package
#   import "runtime/pprof"; pprof.StartCPUProfile(f); defer pprof.StopCPUProfile()
go tool pprof your_binary cpu.prof

Paste the top ten to fifteen lines of whichever output you get, ranked by cumulative time or allocation count, not the whole dump. A real profile for a busy service can run to thousands of lines, and paging all of it into the model's context window buries the ten lines that actually matter under nine hundred that don't.

What goes into a performance-optimization prompt?

Six things, and only one of them is the code you want changed.

1. The measured numbers. Real profiler output or timing figures, not a description like "it's slow." A model told "this endpoint feels sluggish" has nothing to target. A model shown that one function accounts for sixty percent of cumulative time has exactly one place to start.

2. The one constraint that matters. Latency (p50 or p95), throughput (requests per second), memory ceiling, or cost per unit of work. Pick one. Optimizing for all four at once produces a prompt that argues with itself, because the changes that help one commonly hurt another: a cache trades memory for latency, batching trades latency for throughput.

3. A concrete target. "Fast enough" needs a number: p95 under 200ms, not "significantly faster." Without a number, the model cannot tell you when to stop, and neither can you.

4. Correctness invariants. What must not change: exact output values, ordering guarantees, thread-safety assumptions, the existing test suite. A change that hits the latency target by returning slightly wrong answers has not solved your problem.

5. What you already tried and ruled out. So the model does not spend its one shot re-suggesting an idea you already measured as worse. Listing a rejected attempt with the reason it failed is a form of few-shot prompting used defensively: two concrete rejections teach the model your constraints faster than another paragraph of prose.

6. The exact benchmark command. So verifying the model's claim is copy-paste, not a research project of its own. If you cannot hand over a one-line command that reproduces the measurement, you are not ready to trust any answer against it.

How do you stop the model from inventing a speedup it never measured?

By making the format forbid it. Left unconstrained, a model will describe a change and then narrate a claimed improvement, "this cuts allocations by half," "this should be forty percent faster," as if the sentence itself were evidence. It is not. Text generation is not code execution, and unless the tool you are using actually ran the benchmark and is quoting its real output back to you, any specific number in its answer is a guess wearing a lab coat.

The fix is the same one that works for code review: demand a fixed field per proposed change, and make one of the fields a falsifier.

OUTPUT FORMAT — for each proposed change:

  targets            Which line or function from the profile above this
                      addresses. Must exist in the profile you pasted.
  mechanism           Why this should cost less, in profiler terms: fewer
                      allocations, one pass instead of two, an avoided
                      syscall, a smaller working set.
  predicted_effect    Qualitative only — "fewer allocations", "O(n) instead
                      of O(n^2)". Never a percentage or a specific time
                      saved. You have not run this.
  verify              The exact benchmark command to run, using the same
                      harness and input size as the baseline.
  risk                What could break, and which existing test would
                      catch it if it did.

RULES
  - If you cannot name which profiled line `targets` addresses, say so
    instead of proposing the change.
  - Never state a measured number in this response. State a mechanism
    and a command instead.

Requesting structured output here is not cosmetic. A fixed schema makes a missing mechanism visible the moment the field comes back empty, instead of hidden inside a fluent paragraph that sounds like it explained something.

What does a complete measure-first optimization prompt look like end to end?

Here it is assembled. Fill the angle brackets, paste your real profile, and keep the rules at the bottom no matter how short the rest gets.

ROLE
You are optimizing a specific hot path, not rewriting the module. Only
propose changes that trace to a line in the profile below.

PROFILE (top offenders, from <profiler + command used>)
<paste top 10-15 lines: file:line or function, % of total time or bytes>

CONSTRAINT
<pick exactly one: p95 latency | throughput (req/s) | peak memory | cost/call>

TARGET
<a number, e.g. "p95 under 200ms" or "under 40MB peak RSS">

INVARIANTS
  Output must remain identical for: <specific cases>
  Ordering guarantee: <e.g. "results stay in insertion order">
  Concurrency assumption: <e.g. "called from N workers concurrently">
  Must still pass: <test suite or command>

ALREADY TRIED, RULED OUT
  - <change> — measured <result>, rejected because <reason>

BENCHMARK HARNESS (the only thing that proves a change worked)
<exact command, same data size and shape as the profile above>

OUTPUT FORMAT
<paste the field list from the previous section>

RULES
  - Propose at most 2 changes per pass. Rank them.
  - State a mechanism, never a measured number.
  - If nothing in the profile explains the reported slowness, say so and
    ask for a wider profile instead of guessing at a fix.

What changes should you trust, and which should you verify twice?

Trust drops fast the further a proposed change sits from something the profile actually shows you.

Kind of changeHow much to trust it before you measureWhy
Removing work the profile flags as redundant (duplicate computation, an N+1 call visible in the diff)HighFully explained by the numbers you pasted
Swapping a documented O(n²) pattern for a hash-based one, on a loop the profile flagsHighAlgorithmic, and checkable by reading the code
Caching or memoizationMediumOnly correct if the model can see your invalidation rule, and it usually cannot
Concurrency or parallelism rewritesLow without runtime factsDepends on whether the workload is I/O- or CPU-bound, which a text profile alone under-specifies
"Rewrite this in a faster language" or an architecture changeLowRarely supported by the evidence in a single profile; scope creep dressed as a fix
Any claimed percentage with no attached benchmark commandZeroNot measured, not falsifiable, do not ship it

How do you verify the claimed improvement actually happened?

The same way you verified the problem existed: run the benchmark, not the explanation.

Re-run the exact same harness. Same machine, same data size and shape, more than once. A single run is noise, not a result; compare medians across several runs, not a best-of-one number that happened to land well.

Check correctness before you check speed. Existing tests green first. For anything sampling-based or concurrent, run it enough times to catch a regression that only shows up occasionally, not once.

Log the rejected attempts as concretely as the accepted one. This is what stops the next pass, yours or the model's, from proposing the same idea you already measured as worse.

That figure has nothing to do with AI. It is a human, using go tool pprof, finding the actual bottleneck in a slow algorithm and fixing exactly that. It is worth keeping in view anyway, because it is what the measure-first loop is capable of when the target is real: the win came from correctly identifying where the time went, not from a cleverer-sounding rewrite. Your own number, whatever it turns out to be, only exists once you run your own benchmark.

This loop, measure, hypothesize, change, measure again, is a narrower case of a more general discipline: plan, implement, verify, review. If you want that fuller loop for AI-assisted coding beyond just performance work, see pair programming with AI: a working method. And the same falsifiable-evidence standard applies on the other side of a change, once it is written: our guide to prompting for a genuinely useful code review covers demanding a trigger and a repro step from a reviewer the same way this post demands a verify command from an optimizer.

What should you never hand to AI for "performance" reasons?

Anything where a wrong guess about the hot path costs more than the speedup is worth. Authentication and token-comparison code, where a "faster" comparison can quietly become timing-unsafe. Code inside a lock, where reordering for speed can change what "correct" even means. Floating-point behavior other code already depends on. Anything the profile you pasted does not actually implicate, no matter how confidently a model volunteers a fix for it.

There is also a category question worth being blunt about. If what you want is a tool that runs your code, profiles it, and benchmarks a change automatically, that is a different product from a prompt, and this post has not been describing one.

What we do is the layer underneath: a prompt-enhancement platform, web app, browser extensions, and an MCP server at https://mcp.prompt-architects.com/mcp, that turns the measure-first template above into a saved, reusable brief in a Prompt Library with Global Variables for the parts that change per project. Over MCP it runs inside Claude Code, Cursor, and Codex CLI, so the template is a slash command rather than a document you keep hunting for. There is a free plan, built-in AI with no API key required, and current pricing starts at $4.99/mo for Pro at the time of writing. It will not profile your code, will not run your benchmark, and will not tell you your program is faster. It will stop you retyping the brief that makes the model's answer worth checking.

Free Chrome Extension

Stop rewriting prompts. Start shipping.

Works with ChatGPT, Claude, Gemini, Grok, Midjourney, Ideogram, Veo3 & Kling. 4.8★ on the Chrome Web Store.

Create An Account

Frequently asked questions

Free Chrome Extension

Stop rewriting prompts. Start shipping.

Works with ChatGPT, Claude, Gemini, Grok, Midjourney, Ideogram, Veo3 & Kling. 4.8★ on the Chrome Web Store.

Create An Account