⚡️ Faster fc.entityGraph on generate#7045
Merged
Merged
Conversation
`buildEntityStepArbitrary` called `draftNextProductionState(lastState)` once purely to read the current entity and the per-type existing counts, then called it again inside `.map` to do the real mutation. The first call is wasted work: it allocates a full clone of the produced-links state plus several closures, all immediately discarded. `chainUntil` runs this per entity (~100x per construction), so it is hot. Read the two needed values directly instead — the current entity is `lastState.toBeProducedEntities[lastState.nextIndex]` and the existing count is `lastState.producedLinks[targetType].length` (the fresh draft has not enqueued anything yet, so the lengths are identical). The mutable draft is now built lazily, only inside the `.map`. Also drops a dead `countsInTargetType` object that was populated but never read and the two now-unused getter closures. Generated values are unchanged. https://claude.ai/code/session_01SRx4yxfDXbBWd5JDy7nrtz
🦋 Changeset detectedLatest commit: f299afe The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
@fast-check/ava
fast-check
@fast-check/jest
@fast-check/packaged
@fast-check/poisoning
@fast-check/vitest
@fast-check/worker
commit: |
Contributor
⏱️ Benchmark ResultsClick to expand |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #7045 +/- ##
==========================================
+ Coverage 94.86% 94.88% +0.01%
==========================================
Files 212 212
Lines 5884 5943 +59
Branches 1543 1562 +19
==========================================
+ Hits 5582 5639 +57
- Misses 294 296 +2
Partials 8 8
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
dubzzz
commented
Jun 11, 2026
dubzzz
enabled auto-merge (squash)
June 11, 2026 07:27
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
This is a performance-only change to
fc.entityGraph. Generated values (graph topology and entity data) are unchanged — only the speed ofgenerateimproves. No public API change, no behavior change for end users.entityGraphis by far the most expensive arbitrary in the suite, so this is a welcome reduction in per-generatecost.Measured improvement (interleaved separate-process A/B, median of medians):
entityGraph({ node: { id: integer() } }, { node: { linkTo: { arity: 'many', type: 'node' } } }): ~−8% ns/op (≈75.3 → ≈69.1 µs/op).employee/teamschema (0-1manager,1team): ~−13% ns/op (≈47.5 → ≈41.3 µs/op).Why
buildEntityStepArbitrarycalleddraftNextProductionState(lastState)once purely to read the current entity (getCurrentEntity()) and the per-type existing counts (getExistingEntityCount(targetType)), and then called it a second time inside.mapto perform the actual mutation. That first call is wasted work: it allocates a full clone of the produced-links state (Object.assign(Object.create(null), …)) plus several closures, all immediately discarded.chainUntilinvokes this once per produced entity (~100× per construction), so it sits squarely on the hot path.The two values it read are trivially derivable from
lastStatewithout any allocation:lastState.toBeProducedEntities[lastState.nextIndex];lastState.producedLinks[targetType].length— the freshly-drafted state has not enqueued anything yet, so the lengths are identical (verified, see below).So the preparation phase now reads those directly, and the mutable draft is built lazily only inside the
.mapwhere the real mutation happens. While there, this also removes a deadcountsInTargetTypeobject that was populated but never read, and the two now-unused getter closures on the draft object (fewer closures allocated per remaining step).Scope, correctness and impact
fast-check: patch,⚡️ Faster fc.entityGraph on generate) is included.OnTheFlyLinksForEntityGraphArbitrary.ts) plus the changeset.entityGraphintegration spec and the internal_internalsspecs (OnTheFlyLinks,UnlinkedToLinkedEntities,InitialPool,UnlinkedEntities) all pass, and the full suite is green. Output was additionally checked to be structurally identical tomain(800 generations, 0 fingerprint mismatches), so the eliminated call provably only read values equal to the direct reads — no new test is needed because the distribution is unchanged. The removed getters had no other callers (grep-confirmed, including tests).Checklist
— Don't delete this checklist and make sure you do the following before opening the PR
pnpm run bumpor by following the instructions from the changeset bot🐛(vitest) Something...) when the change targets a package other thanfast-checkGenerated by Claude Code