Skip to content

[Fusion] Batch path segment pool access and keep the local pool across requests - #10548

Merged
tobias-tengler merged 3 commits into
mainfrom
tte/fusion-lock-hotspots
Oct 16, 2026
Merged

tobias-tengler merged 3 commits into
mainfrom
tte/fusion-lock-hotspots

Conversation

@tobias-tengler

@tobias-tengler tobias-tengler commented Oct 15, 2026 •

Copy link
Copy Markdown
Member

Every result path borrows an int[] from the process-wide PathSegmentPool, and every rent and return takes its SpinLock. A request with nested lookups needs hundreds to thousands of paths, so with a few busy cores the threads spin on that lock, and OperationPlanContext.Clean returns every array through it once more.

The shared pool now rents and returns arrays in batches of 32, and the request-local pool stays alive in the pooled FetchResultStore: after a request it marks its arrays free again and returns only what exceeds PathSegmentLocalPoolCapacity (64 by default, 1 the minimum). A request touches the shared lock a few times instead of twice per path. The local pool shrinks its bookkeeping after a burst and drops its arrays when the shared pool has been replaced; the shared pool rejects arrays of another length without unbalancing its in-use count. A result document is detached from the local pool when the store hands its memory to the operation result, so a path read after execution is allocated and never touches another request's arrays. The local pool drops returns once every owned array is free, so a straggling return after a reset cannot hand the same array to two paths, and the shared pool holds its lock for at most 32 arrays per rent or return.

Benchmarks

Each thread is one pooled request context with its own local pool, running requests back to back. A request rents the given number of paths, returns half and ends. Mean is the time per request on one thread.

Path segments, 1 thread, 32 paths per request

Method Version Mean Ratio Allocated Alloc Ratio
Rent, return, reset main 588.2 ns 1.00 51 B 1.00
Rent, return, reset PR 145.6 ns 0.25 3 B 0.06

Path segments, 1 thread, 1024 paths per request

Method Version Mean Ratio Allocated Alloc Ratio
Rent, return, reset main 17.034 µs 1.00 51 B 1.00
Rent, return, reset PR 11.093 µs 0.65 3 B 0.06

Path segments, 4 threads, 32 paths per request

Method Version Mean Ratio Allocated Alloc Ratio
Rent, return, reset main 11.678 µs 1.00 197 B 1.00
Rent, return, reset PR 0.191 µs 0.02 5 B 0.03

Path segments, 4 threads, 1024 paths per request

Method Version Mean Ratio Allocated Alloc Ratio
Rent, return, reset main 253.519 µs 1.00 200 B 1.00
Rent, return, reset PR 47.240 µs 0.19 5 B 0.03

Path segments, 16 threads, 32 paths per request

Method Version Mean Ratio Allocated Alloc Ratio
Rent, return, reset main 29.349 µs 1.00 778 B 1.00
Rent, return, reset PR 1.181 µs 0.04 6 B 0.01

Path segments, 16 threads, 1024 paths per request

Method Version Mean Ratio Allocated Alloc Ratio
Rent, return, reset main 308.177 µs 1.00 778 B 1.00
Rent, return, reset PR 188.465 µs 0.61 12 B 0.02

@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Patch coverage

98.3% of changed lines covered (174/177)

File Covered Changed Patch %
…/Fusion/src/Fusion.Execution/Text/Json/PathSegmentPool.cs 76 79 96.2% 🟡
…/src/Fusion.Execution/Execution/OperationPlanContext.cs 2 2 100.0% 🟢
…/Execution/Results/FetchResultStore.Pooling.cs 13 13 100.0% 🟢
…/src/Fusion.Execution/Execution/Results/FetchResultStore.cs 1 1 100.0% 🟢
…/src/Fusion.Execution/Text/Json/CompositeResultDocument.cs 2 2 100.0% 🟢
…/src/Fusion.Execution/Text/Json/PathSegmentLocalPool.cs 79 79 100.0% 🟢
…/Fusion/src/Fusion.Execution/Text/Json/PathSegmentMemory.cs 1 1 100.0% 🟢
Uncovered changed lines (JSON)
{
  "sha": "f63946282819db97eee5a86f68c594e7f818a02f",
  "files": [
    { "path": "src/HotChocolate/Fusion/src/Fusion.Execution/Text/Json/PathSegmentPool.cs", "ranges": [[124, 126]] }
  ]
}

Project coverage: 59.0% (310997/527501 lines)

@tobias-tengler tobias-tengler changed the title [Fusion] Batch path segment pool access and write JSON results in one pipe write Batch path segment pool access and write JSON results in one pipe write Oct 15, 2026
@tobias-tengler
tobias-tengler force-pushed the tte/fusion-lock-hotspots branch from 648a1f7 to 2a163cf Compare October 15, 2026 16:02
…s requests

The shared path segment pool hands out and takes back arrays in batches of 32, and
the request-local pool stays alive in the pooled FetchResultStore, keeping up to the
configured capacity of arrays between requests. The local pool shrinks its
bookkeeping after a burst and drops its arrays when the shared pool is replaced, and
the shared pool checks array lengths and keeps its in-use count balanced. A result
document allocates its paths once its pool has been reset for the next request.
@tobias-tengler
tobias-tengler force-pushed the tte/fusion-lock-hotspots branch from 1482252 to 74ff48d Compare October 15, 2026 17:10
@tobias-tengler tobias-tengler changed the title Batch path segment pool access and write JSON results in one pipe write [Fusion] Batch path segment pool access and keep the local pool across requests Oct 15, 2026
@tobias-tengler
tobias-tengler marked this pull request as ready for review October 15, 2026 17:15
Copilot AI balanced review requested due to automatic review settings October 15, 2026 17:15

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

…d pool lock holds

The local path segment pool now drops returns once every owned array is free, so
a straggling return after a reset cannot put a duplicate into the free list, and
renting from a disposed pool throws instead of leaking.

The shared pool rents and returns in chunks of 32 per lock acquisition, so a
large request teardown no longer holds the spin lock for its whole span, and the
return logging skips null entries.

Result documents are detached from the request scoped pool when the store hands
its memory owners to the operation result, which replaces the generation guard.
@tobias-tengler
tobias-tengler merged commit 3a7d93b into main Oct 16, 2026
158 checks passed
@tobias-tengler
tobias-tengler deleted the tte/fusion-lock-hotspots branch October 16, 2026 06:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants