Skip to content

refactor(toolkit-lib): compute each resource's property hash once per refactor - #1827

Open
Adityaj0 wants to merge 1 commit into
aws:mainfrom
Adityaj0:perf/refactor-digest-reuse-property-hashes
Open

refactor(toolkit-lib): compute each resource's property hash once per refactor#1827
Adityaj0 wants to merge 1 commit into
aws:mainfrom
Adityaj0:perf/refactor-digest-reuse-property-hashes

Conversation

@Adityaj0

Copy link
Copy Markdown

Fixes #1824

Reason for this change

A RefactoringContext computes digests four times for one environment: resourceMoves handles the deployed and the local stacks in the 'direct' direction, then structuralOverrides repeats both in the 'opposite' direction.

Only the dependency half of a digest depends on the direction. The other input — hashObject(stripReferences(stripConstructPath(resource), exports)), which is where nearly all the CPU goes because it walks and hashes the resource's whole property tree — is a pure function of the resource and the stacks' exports, and is byte-identical in every pass. It is recomputed from scratch in each of them.

Description of changes

Added an optional PropertyHashCache (a Map keyed by the resource object) to computeResourceDigests, consulted before hashing and populated after. RefactoringContext creates one in its constructor and threads it through resourceMoves, structuralOverrides and resourceDigests, so all four passes share it.

Two deliberate choices:

  • The cache is scoped to one operation, not module state. Keys are the resource objects themselves, so a longer-lived cache could outlive the templates it was built from and go stale if a caller mutated one. Owning it in the constructor bounds its lifetime to the refactor.
  • The parameter defaults to a fresh Map. Every existing caller of computeResourceDigests — including the tests — behaves exactly as before without changes.

Digests are unchanged; only how often they are computed changes.

Description of how you validated changes

  • New regression test asserting that digests computed with a cache shared across both graph directions are identical to digests computed without one, for both directions.

  • All 55 tests under test/api/refactoring/ pass.

  • Benchmark — synthetic CDK-shaped templates (nested policy documents, tags, Ref/Fn::GetAtt cross-references, DependsOn, aws:cdk:path on every resource), timing exactly the four computeResourceDigests calls a RefactoringContext performs. Cold node process per measurement, min of 7 runs:

    template shape JSON size per side before after improvement
    1 stack × 200 resources 0.6 MiB 48.6 ms 32.6 ms 32.9%
    5 stacks × 200 resources 3.0 MiB 238.2 ms 156.4 ms 34.4%
    10 stacks × 400 resources 22.2 MiB 1542.1 ms 961.6 ms 37.6%

    Each measurement is a single cold execution, so the cache starts empty exactly as in a real CLI invocation: two passes fill it, two read from it. Timing repeated iterations inside one process would report ~80%, but only because the cache is already warm by then — that number would not reflect what a user sees.

Note: #1823 covers the deep clone in stripConstructPath, a separate ~20-25% in the same code path. The two changes are independent and compose.

Checklist

  • This change contains a major version upgrade for a dependency and I confirm all breaking changes are addressed
    • Release notes for the new version:

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache-2.0 license

… refactor

A `RefactoringContext` computes digests four times for one environment:
`resourceMoves` does the deployed and the local stacks in the 'direct'
direction, and `structuralOverrides` repeats both in the 'opposite'
direction.

Only the dependency part of a digest depends on the direction. The other
input — the hash of the resource's own reference-stripped properties,
which is where nearly all the work is — is identical in all four passes,
and is recomputed from scratch every time.

Compute it once per resource and share it across the four passes, via a
cache owned by the RefactoringContext and threaded through the call
chain. The cache is scoped to a single operation rather than held in
module state, so it cannot outlive the templates it was built from.

Digests are unchanged; only how often they are computed changes.

Measured on synthetic CDK-shaped templates (cold process, min of 7):
33-38% off the digest phase of a refactor.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
auto-merge was automatically disabled August 12, 2026 08:36

Head branch was pushed to by a user without write access

@Adityaj0
Adityaj0 force-pushed the perf/refactor-digest-reuse-property-hashes branch from cac3b14 to 9e4f3fa Compare August 12, 2026 08:36
@Adityaj0 Adityaj0 changed the title perf(toolkit-lib): compute each resource's property hash once per refactor refactor(toolkit-lib): compute each resource's property hash once per refactor Aug 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

toolkit-lib: cdk refactor hashes every resource's properties four times (~35% of the digest phase)

1 participant