Ask an Agent to Investigate Slow Code Before Requesting a Rewrite

Drafted and reviewed using AI assistance.

On this page

Give a performance investigation a repeatable workload, a cost you care about and a place to record measurements. Ask the agent to distinguish a plausible explanation from an observed bottleneck before changing the implementation.

This guide uses an original fictional Python task and an empty investigation record. The commands target Python 3.14. They are instructions you can adapt, not commands executed for this article or a measured speed comparison.

Freeze the workload and expected result

Imagine a small program grouping record sizes by tag. Its current implementation scans all records once for each distinct tag:

# slow_case.py — educational Python 3.14 example

def totals_by_tag(records):
    tags = sorted({record["tag"] for record in records})
    return {
        tag: sum(record["size"] for record in records if record["tag"] == tag)
        for tag in tags
    }


def build_records(count=4000, groups=40):
    if count < 0 or groups < 1:
        raise ValueError("count must be non-negative and groups must be positive")
    return [
        {"tag": f"tag-{i % groups:02d}", "size": (i % 5) + 1}
        for i in range(count)
    ]


if __name__ == "__main__":
    data = build_records()
    totals_by_tag(data)

For this example, inputs are lists of dictionaries with string tags and non-negative integer sizes. Results use sorted tag insertion order and must not change the input records. File loading, a network service and display rendering are outside this function-level investigation.

Keep a tiny reference case beside the larger workload:

Input:
[{tag: red, size: 4}, {tag: blue, size: 5}, {tag: red, size: 7}]

Expected totals:
blue: 5
red: 11

Expected output order:
blue, then red

These are expected values calculated from the fictional input. They are not a saved test run.

Turn “slow” into a question

Copy this investigation card:

Operation and exact code revision:
Python implementation/version:
Machine and relevant environment:

Workload:
- Input construction:
- Record count and distinct tags:
- Preparation included or excluded:
- Expected result and ordering:

Cost to investigate:
[Whole operation / one function / allocation / another explicit cost.]

Current observed measurements:
[Leave empty if none; do not substitute an estimate.]

Candidate explanations:
Evidence needed to distinguish them:
Allowed change after investigation:

A large input alone does not identify the problem. Keep both record count and tag count visible: this implementation's structure repeats the record scan for each tag, but how much that matters in your environment is a measurement question.

Separate profiling from timing

The Python 3.14 profiler documentation distinguishes execution profiling from benchmarking. Use profiling to investigate where execution spends work; use a separate timing setup when comparing the operation's elapsed cost.

For the educational file, an illustrative profiling command is:

python3.14 -m cProfile -s cumulative slow_case.py

This profiles the script, including its workload construction. Inspect function-level entries with that scope in mind. A high cumulative entry includes called functions; it is not a standalone measure you can add to every child's time to get a new total.

For repeated timing that prepares the input outside the timed statement, the Python 3.14 timeit interface supports setup code:

python3.14 -m timeit -s 'from slow_case import build_records, totals_by_tag; data = build_records()' 'totals_by_tag(data)'

Run commands only with the intended interpreter and a copy of the educational file. If your runtime is another Python version, check that version's documentation rather than treating the moving /3/ documentation as the same API contract.

Keep a hypothesis tree

Observed symptom: operation seems slow
|
+-- Input preparation dominates?
|   Inspect script profile; measure preparation separately if relevant.
|
+-- Repeated scans dominate?
|   Vary record count and distinct-tag count independently.
|   Inspect function/call evidence before choosing an aggregation change.
|
+-- The cost is elsewhere in the application?
    Measure the actual boundary: loading, function work and rendering.
    Do not infer whole-application behavior from this isolated function.

The branches are candidate explanations. Do not cross out alternatives because the code looks inefficient; use evidence from the operation that matters to the user.

Propose one change and keep its comparison fair

If measurements point to repeated scans, a candidate is to aggregate sizes in one pass and produce sorted output afterward. Preserve the input contract, totals, ordering and non-mutation behavior. Do not combine that investigation with a new cache, concurrency layer and storage format in one untraceable rewrite.

Use the same input data and interpreter for the comparison. Keep setup boundaries consistent, retain individual timing results and describe any changed conditions. A profiler report can guide the choice; it should not become a claimed end-to-end speedup.

Workload Version Timing boundary Actual timing record Correct totals/order checked?
4,000 records; 40 tags Baseline totals_by_tag(data) only — —
Same data Candidate Same boundary — —
4,000 records; 4 tags Baseline/candidate separately Same boundary — —
400 records; 40 tags Baseline/candidate separately Same boundary — —

No entries are filled because no benchmark was run for this article. An empty record leaves the next step clear: collect a baseline or return an investigation plan.

Copy the agent request

Investigate totals_by_tag using the supplied Python 3.14 example.
First confirm the input assumptions and expected totals/order.

Separate input construction from the function cost.
Use a profile to investigate candidate bottlenecks and record a
consistent timing boundary for any later baseline/candidate comparison.

Do not rewrite the architecture before reporting the evidence.
If repeated scans are the supported cause, propose one aggregation change
that preserves totals, sorted output and input non-mutation.

Return workload, environment, actual observations, remaining hypotheses
and one next step. If you did not run a measurement, give the plan and
leave result fields empty rather than reporting an estimated improvement.

A useful investigation can end with a well-defined next measurement. Once evidence supports a change, keep the revision and its comparison small enough that you can explain what improved, under which conditions and which behavior stayed the same.