Skip to content

*** WIP: Create PooledSpanBuilder, strongly based on PooledArrayBuilder #12095

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 6 commits into
base: main
Choose a base branch
from

Conversation

ToddGrun
Copy link
Contributor

@ToddGrun ToddGrun commented Aug 9, 2025

*** Note: This is in draft mode until I receive verification that I'm not missing a data structure that satisfies all my span and pooling desires. Also, I'm on vacation until the 18th, so I'm creating this before my trip in the hopes of having juicy feedback when I get back.

Insertion PR: https://dev.azure.com/devdiv/DevDiv/_git/VS/pullrequest/660373

I've found myself in need of a pooled array data structure that gives me access to an underlying span.

I've considered:

  1. PooledArrayBuilder:
    -- Doesn't have an underlying array for <= 4 elements.
  2. PooledList
    -- Backing List doesn't provide access to the underlying array (it does in Net5 and higher though)

Perhaps there is another data structure which can do this, in which case the majority of this PR can be nuked. If not, it was fairly easy to augment the PooledArrayBuilder class to rip out the inline elements.

This is in response to some allocations I'm seeing in the MetadataBuilder and MetadataCollection classes in the razor speedometer cohosting completion test. This change allows those classes to work better with spans, avoiding allocations.

Best reviewed by commit:
Commit 1: Change PooledArrayBuilder to PooledSpanBuilder
Commit 2: Move PooledSpanBuilder implementation to appropriately named files
Commit 3: Add back the PooledArrayBuilder in all it's beauty
Commit 4: Tests (thanks copilot!)
Commit 5: Add in product usage of the new classes

*** Before changes ***
image

*** With changes, results from speedometer run linked above ***
image

Copy link
Member

@davidwengier davidwengier left a comment

Choose a reason for hiding this comment

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

lol, I reviewed commit-at-a-time so my comments disappear if you just look at the changes as a whole.

Only the one on the indexer is important though, it pretty much all LGTM

}

return GetInlineElement(index);
return _array[index];
Copy link
Member

Choose a reason for hiding this comment

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

Feels like the indexer should still validate against count, since the array length could be larger and therefore the underlying indexer can't be relied on to throw.

/// continue to be used, even if the arrays shrinks and has fewer than 4 elements.
/// Wraps a pooled array but doesn't allocate it until it's needed. Provides
/// access to the backing array as a <see cref="Span{T}"/>.
/// Note: Disposal ensures the pooled array is returned to the pool.
/// </summary>
[NonCopyable]
Copy link
Member

Choose a reason for hiding this comment

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

Is it worth adding some notes about capacity behaviour? ie adding could resize and cause an array copy, and that nothing fancy is done to prevent LOH etc. so there are better choices for really big arrays. Though I might just be being a bit paranoid :)

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