fix(core): performance improvements#1964
Open
christian-bromann wants to merge 11 commits intomainfrom
Open
Conversation
|
@langchain/langgraph-checkpoint
@langchain/langgraph-checkpoint-mongodb
@langchain/langgraph-checkpoint-postgres
@langchain/langgraph-checkpoint-redis
@langchain/langgraph-checkpoint-sqlite
@langchain/langgraph-checkpoint-validation
create-langgraph
@langchain/langgraph-api
@langchain/langgraph-cli
@langchain/langgraph
@langchain/langgraph-cua
@langchain/langgraph-supervisor
@langchain/langgraph-swarm
@langchain/langgraph-ui
@langchain/langgraph-sdk
commit: |
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.
Summary
Performance optimizations for
@langchain/langgraph-coretargeting the hottest functions identified by profiling the test suite. These findings were generated using the zeitzeuge profiling tool, which analyzed CPU profiles across the full test run (1085 tests, 60.8s total duration).1. Eliminated repeated
Object.values()allocationsFiles:
pregel/loop.ts,pregel/runner.tsObject.values(this.tasks)was called ~11 times pertick()invocation, each creating a fresh array from the same unchanged record. Now cached into a local variable (newTaskList,finishTaskList,matchTaskList,allTasks) and reused throughout. Same treatment applied inrunner.tsforObject.values(this.loop.tasks).2. Hoisted per-call allocations in XXH3 hash
File:
hash.tsTextEncoder, thebswap64scratchDataView/ArrayBuffer, and thehexDigestclosure were all being created on everyXXH3()call. They're now module-level singletons, eliminating repeated allocations on a hot path.3. Converted arrays to
Sets for O(1) lookupsFiles:
pregel/utils/config.ts,pregel/algo.tsCOPIABLE_KEYSandCONFIG_KEYSarrays were converted toSets;.includes()replaced with.has().RESERVEDarray usage in_applyWriteswas replaced with a module-levelRESERVED_SETfor O(1) checks.4. Replaced
Object.entries()withfor...inloopsFile:
pregel/utils/config.tsThree
Object.entries()calls inensureLangGraphConfigeach allocated intermediate[key, value]tuple arrays. Switched tofor...inwith direct property access to avoid those allocations.5. Skipped stack trace capture for
EmptyChannelErrorFile:
errors.tsEmptyChannelErroris thrown for control flow (checking if a channel has data), not for real exceptions. The constructor now temporarily setsError.stackTraceLimit = 0beforesuper()to skip V8's expensive stack-walking, then restores it.6. Cached sort comparator allocations in
_applyWritesFile:
pregel/algo.tsThe sort comparator was calling
.slice(0, 3)on task paths for every comparison (O(n log n) comparisons = 2n log n array allocations). Now pre-computed into apathCacheMap before sorting.7. Combined multiple task iterations into a single pass
File:
pregel/algo.ts_applyWritespreviously iterated tasks separately for: checkingbumpStep, updatingversions_seen, and collectingchannelsToConsume(viaflatMap+filter). These were merged into a single loop.8. Pre-indexed
pendingWritesfor O(1) lookupsFile:
pregel/algo.tsIntroduced
_indexPendingWrites()which builds aPendingWritesIndexwith:nullResume— the first null-task resume valueresumeByTaskId— aMap<string, unknown[]>for per-task resume valuessuccessfulWriteTaskIds— aSet<string>for quick "has successful write" checksThis index is computed once in
_prepareNextTasksand passed through to_prepareSingleTaskand_scratchpad, replacing O(N*M) linear scans with O(1) lookups.9. Converted recursive
tick()to awhileloopFile:
pregel/loop.tsThe recursive
return this.tick({ inputKeys })call was replaced withcontinueinside awhile (true)loop, avoiding async state machine re-entry overhead and potential stack issues.10. Gated debug output on stream mode
File:
pregel/loop.tsDebug checkpoint and task output is now only generated when
this.stream.modes.has("debug")or the relevant mode is active, avoiding expensive serialization work when no one is listening.11. Fixed O(N^2) object spread in
_first()File:
pregel/loop.tsThe
versions_seen[INTERRUPT]update previously spread the entire object inside a loop (O(N^2) allocations). Now spreads once into a newinterruptSeenobject, then mutates it directly — also fixing a subtle bug with shallow-copied checkpoint references.12. Cached
isResuminggetterFile:
pregel/loop.tsThe
isResuminggetter (which iterates channel versions and checks config/metadata) was being called multiple times in_first(). Now cached into a local variable for the first two accesses, with a fresh read for the finalCONFIG_KEY_RESUMINGassignment.13. Optimized
Promise.racearray constructionFile:
pregel/runner.tsInstead of
Promise.race([...Object.values(executingTasksMap), ...abortPromise, barrier.wait])(which spreads into a new array every iteration), the promises array is now built once withpush()calls.