Skip to content

perf(downgrader): skip decoding and unescaping plain $refs, fast path plain objects - #85

Merged
dinwwwh merged 1 commit into
mainfrom
claude/project-thread-2u3755
Oct 24, 2026
Merged

dinwwwh merged 1 commit into
mainfrom
claude/project-thread-2u3755

Conversation

@dinwwwh

@dinwwwh dinwwwh commented Oct 24, 2026

Copy link
Copy Markdown
Member

Requested by dinwwwh · project thread

Since #69, 3.1 to 3.0 resolves the target of every kept schema $ref to read its looseness, which made $ref-heavy docs 17-35% slower in wall time. CodSpeed's instruction counts don't show it. This takes back part of that with three small changes in packages/downgrader/src/shared.ts:

  • find calls decodeURIComponent only when the ref contains %.
  • find unescapes ~1 and ~0 only when a token contains ~.
  • isRecord checks proto === Object.prototype first, so plain objects skip Object.keys(proto), which allocated an array on every call.

Output is unchanged: the 252 existing tests pass, and the before and after builds give byte-identical JSON on every doc below.

Bench

Both versions bundled with esbuild and run in one Node 22 process with --expose-gc, gc before each timed run, order shuffled each round, median of 100-200 rounds, downgradeSpecV31ToV30:

Doc Before After Change
generated api, 100 resources (repo bench) 16.26 ms 15.07 ms -7.3%
Discord API 3.1 20.94 ms 19.46 ms -7.1%
official 3.1 corpus 0.597 ms 0.542 ms -9.2%
oRPC test document 0.345 ms 0.329 ms -4.8%
GitHub REST 3.1 (13 MB) 146.6 ms 148.1 ms within noise

The GitHub doc shifts between -10% and +1% across runs, since time on it goes mostly to allocation and GC. This doesn't do the bigger lazy-looseness rework.


Generated by Claude Code

…th plain objects

`find` now calls `decodeURIComponent` only when the ref holds a `%`, and
unescapes a token only when it holds a `~`. `isRecord` checks for
`Object.prototype` first, so plain objects skip `Object.keys(proto)`.
Output is unchanged; 3.1 to 3.0 is about 7% faster on `$ref`-heavy docs.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GiDmqeN32Hyhg994YCcgxd
@dinwwwh dinwwwh self-assigned this Oct 24, 2026
@macroscopeapp

macroscopeapp Bot commented Oct 24, 2026

Copy link
Copy Markdown

Approvability

Verdict: Approved at f7be134

Macroscope's review found this PR approvable — This is a focused five-line optimization in the downgrader that avoids unnecessary decoding, unescaping, and prototype-key allocation while retaining the existing paths when those operations are needed. It introduces no new capability or schema/API change and has a narrow, self-contained runtime impact.

You can add or adjust custom eligibility rules. Learn more.

@codecov

codecov Bot commented Oct 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ No new issues found.

Reviewed changes

  • isRecord fast path — proto === Object.prototype short-circuits before the generic null-prototype check in packages/downgrader/src/shared.ts:52, avoiding the Object.keys array allocation for plain same-realm objects. The added branch is a subset of what the generic branch already accepted (Object.getPrototypeOf(Object.prototype) === null, zero enumerable own keys), so the predicate is unchanged.
  • find decode skip — packages/downgrader/src/shared.ts:196 only calls decodeURIComponent when the ref contains %. A string without % has no percent-escapes, so decoding is the identity; refs with a malformed % still throw and are caught as undefined exactly as before.
  • find unescape skip — packages/downgrader/src/shared.ts:205 only unescapes ~1/~0 when a token contains ~, and replaceAll on those literals is a no-op otherwise.

Verified locally: pnpm test passes 252/252 (snapshot tests pin converter output, so any behavior change would surface), and uncheck packages/downgrader/src/shared.ts passes oxlint, oxfmt, and tsc.

Pullfrog  | View workflow run | Using deepseek-v4.1-flash (free via Pullfrog for OSS) | 𝕏

@codspeed

codspeed Bot commented Oct 24, 2026

Copy link
Copy Markdown

Merging this PR will improve performance by 20%

1 warning

⚠️ Base and head ran on different runner types

Some benchmarks with significant performance changes were compared across different runner types, so the changes might not come from the code. Run base and head on the same runner type, or use CodSpeed Macro Runners for more stable results.

Learn more about Macro Runners

Open the report in CodSpeed to investigate

⚡ 2 improved benchmarks
✅ 19 untouched benchmarks

Performance Changes

Benchmark BASE HEAD Efficiency
⚡ reference diamond into removed webhooks, 64 levels 2.9 ms 2.4 ms +21.54%
⚡ path item $ref chain in removed components, 200 hops 6.8 ms 5.7 ms +18.48%

Tip

Curious why performance improved? Comment @codspeedbot explain why performance improved on this PR, or directly use the CodSpeed MCP with your agent.


Comparing claude/project-thread-2u3755 (f7be134) with main (c78a39b)

Open in CodSpeed

@dinwwwh
dinwwwh merged commit 70e28ec into main Oct 24, 2026
11 checks passed
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