Skip to content

perf(sdk): cut checkpoint and batch hot-path CPU self-time by ~48%#717

Merged
ParidelPooya merged 1 commit into
mainfrom
perf/sdk-hot-path-optimizations
Jul 14, 2026
Merged

perf(sdk): cut checkpoint and batch hot-path CPU self-time by ~48%#717
ParidelPooya merged 1 commit into
mainfrom
perf/sdk-hot-path-optimizations

Conversation

@ParidelPooya

@ParidelPooya ParidelPooya commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

CPU profiling of the integration examples against the local test runner (with SDK
source-level attribution) showed ~78% of SDK CPU self-time concentrated in three files:
checkpoint-manager.ts, serdes.ts, and batch-result.ts. This PR removes the redundant
work in those hot paths with no wire-format or replay-semantic changes — checkpointed
payload shapes and event histories are byte-identical.

Changes:

  • checkpoint-manager.ts
    • Compute each checkpoint item's wire size once at enqueue via Buffer.byteLength
      (previously JSON.stringify + TextEncoder.encode per item on every processQueue
      pass — for large payloads this re-serialized 100KB+ items repeatedly just to read a length)
    • Drain batches with a cursor + single slice() (previously O(n²) per-item shift())
    • Resolve waiting operations via a hashed-id reverse index (previously MD5-hashed every
      tracked operation on each status change)
    • Hot-path log arguments are now lazy thunks
  • batch-result.ts — success/failure/started counts computed in a single pass at
    construction; succeeded()/failed()/started() memoized. Native #private fields keep
    JSON.stringify output identical to the previous getter-based implementation.
  • concurrent-execution-handler.tsMap-based index join for itemStatuses
    (previously O(n²) find-inside-map)
  • step-id-utils.tshashId memoized (bounded cache, 10k entries)
  • logger.tsDURABLE_VERBOSE_MODE cached at module load (reading process.env is a
    native call, and log() sits in checkpoint hot paths); refreshLogConfig() added for
    callers that mutate the env var after load. run-durable.ts refreshes the cache after its
    assignment, and the testing CLI now calls setupEnvironment before the handler import
    loads the SDK.
  • scripts/profile-sdk.ts (examples) — new profiling tool that runs examples against the
    copied SDK source (same mechanism as the coverage scripts) and emits Chrome
    DevTools-compatible .cpuprofile files with per-SDK-file attribution.

Demo/Screenshots

Profile comparison (identical parameters: 5 iterations, 100µs sampling, same example set):

SDK file Baseline hits Optimized hits Δ
Total SDK self-time 4,438 2,310 −47.9%
utils/checkpoint/checkpoint-manager.ts 2,295 791 −66%
utils/serdes/serdes.ts 1,111 851 −23%
handlers/concurrent-execution-handler/batch-result.ts 581 448 −23%
utils/logger/logger.ts 51 1 −98%
utils/step-id-utils/step-id-utils.ts 39 9 −77%

Reproduce with:
npx tsx scripts/profile-sdk.ts --cpu --examples comprehensive-operations,map/large-scale,parallel/basic,step/with-retry,promise/all --iterations 5

@ParidelPooya
ParidelPooya force-pushed the perf/sdk-hot-path-optimizations branch 5 times, most recently from 3767d27 to 0b21469 Compare July 14, 2026 20:49
Comment thread packages/aws-durable-execution-sdk-js-examples/package.json Outdated
Comment thread packages/aws-durable-execution-sdk-js-examples/scripts/profile-sdk.ts Outdated
CPU profiling of the integration examples against the local runner (with
SDK source-level attribution) showed ~78% of SDK self-time concentrated in
checkpoint-manager.ts, serdes.ts, and batch-result.ts. This change removes
the redundant work in those hot paths without any wire-format or replay
semantic changes:

- checkpoint-manager: compute checkpoint item wire size once at enqueue
  using Buffer.byteLength (was JSON.stringify + TextEncoder per item on
  every processQueue pass), drain batches with a cursor + single slice
  (was O(n^2) shift), resolve waiting operations via a hashed-id reverse
  index (was MD5-hashing every tracked operation per status change), and
  make hot-path log arguments lazy.
- batch-result: compute success/failure/started counts in a single pass at
  construction and memoize succeeded()/failed()/started(). Native #private
  fields keep JSON.stringify output byte-identical.
- concurrent-execution-handler: Map-based index join for itemStatuses
  (was O(n^2) find-inside-map).
- step-id-utils: memoize hashId (bounded cache, 10k entries).
- logger: cache the DURABLE_VERBOSE_MODE flag at module load (with
  refreshLogConfig() for tests) and support thunk data args so disabled
  logging skips argument construction. refreshLogConfig() is exported from
  the public index for callers that mutate the env var after module load:
  run-durable.ts refreshes after its assignment, and the testing CLI's
  setupEnvironment refreshes it (feature-detected for older peer SDKs,
  whose module cache freezes the flag at CLI startup) and also sets the env
  var before the handler import to cover dual-SDK-instance module graphs.

Also adds scripts/profile-sdk.ts (profiles examples against copied SDK
source, same mechanism as the coverage scripts).

Measured with identical profile parameters (5 iterations, 100us sampling):
total SDK self-time 4438 -> 2310 hits (-47.9%); checkpoint-manager -66%,
logger -98%, step-id-utils -77%, batch-result -23%.

Validation: 1057 SDK unit tests, 182 example history-assertion tests, and
2 ESM vitest tests pass; tsc, eslint, prettier clean.
@ParidelPooya
ParidelPooya force-pushed the perf/sdk-hot-path-optimizations branch from 0b21469 to e240d6c Compare July 14, 2026 21:22
@ParidelPooya
ParidelPooya marked this pull request as ready for review July 14, 2026 21:38
@ParidelPooya
ParidelPooya merged commit f4373e8 into main Jul 14, 2026
16 checks passed
@ParidelPooya
ParidelPooya deleted the perf/sdk-hot-path-optimizations branch July 14, 2026 21:38
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants