Skip to content

Use ToImmutableAndClear when using a pooled builder with a known length#63110

Merged
CyrusNajmabadi merged 1 commit intodotnet:mainfrom
CyrusNajmabadi:toImmutableAndClear
Aug 2, 2022
Merged

Use ToImmutableAndClear when using a pooled builder with a known length#63110
CyrusNajmabadi merged 1 commit intodotnet:mainfrom
CyrusNajmabadi:toImmutableAndClear

Conversation

@CyrusNajmabadi
Copy link
Copy Markdown
Contributor

@CyrusNajmabadi CyrusNajmabadi commented Aug 1, 2022

ToImmutableAndClear has two benefits over ToImmutable when the ArrayBuilder was given a known length.

  1. if the number of elements in the builder is the same as the number requested, then this becomes a MoveToImmutable call, which avoids needing to make a new array that hte values are copied to.
  2. the MoveToImmutable then sets teh internal array of the builder back to an empty array with a Count of 0. This means that this ArrayBuilder will always go back to the pool, even if hte number of elements that were added exceed our normal pool cap. e.g. our normal pool cap is 128 elements. But this means that we could add far above that amount, and still get pooling (since we're going to safely pool a builder with 0 elements in it)

So this is best of all worlds. No additional intermediary arrays. No copies. And we always pool, no matter how many elements we computed while using hte scratch array.

Note: i only updated the usages where i could trivially validate that the number of elements added was equal to the number of elements requested.

@CyrusNajmabadi CyrusNajmabadi requested a review from a team as a code owner August 1, 2022 17:07
@ghost ghost added the Area-IDE label Aug 1, 2022
SyntaxEditor editor, CodeActionOptionsProvider fallbackOptions, CancellationToken cancellationToken)
{
using var spansDisposer = ArrayBuilder<TextSpan>.GetInstance(diagnostics.Length, out var spans);
using var _ = ArrayBuilder<TextSpan>.GetInstance(diagnostics.Length, out var spans);
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

no point naming these disposers. our pattern in teh vast majority of other cases is just to use the _ or _1... to signifiy this should not be accessed itself.

Copy link
Copy Markdown
Member

@davidwengier davidwengier Aug 2, 2022

Choose a reason for hiding this comment

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

Is there a point to having the disposers, if the calls are changed to clear? I thought the whole idea was to save the need to call clear, but if you're now calling clear, should they just be removed?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

yes. the disposers are necessary for two reasons:

  1. ensuring we clean up in the event of early return (for example, cancellation).
  2. ensuring the builder gets returned to the pool. ToImmutableAndClear will give the resultant ImmutableArray and clear the builder. but the builder is not returned to the pool.

@ryzngard
Copy link
Copy Markdown
Contributor

ryzngard commented Aug 1, 2022

the MoveToImmutable then sets teh internal array of the builder back to an empty array with a Count of 0. This means that this ArrayBuilder will always go back to the pool, even if hte number of elements that were added exceed our normal pool cap. e.g. our normal pool cap is 128 elements. But this means that we could add far above that amount, and still get pooling (since we're going to safely pool a builder with 0 elements in it)

Trying to understand why resetting to 0 is always good here. Wouldn't that mean we almost always want to call ToImmutableAndClear anyways? Even if it's not the same length as the pool, it still resets to 0 and just calls ToImmutable causing the same additional array.

@CyrusNajmabadi
Copy link
Copy Markdown
Contributor Author

Trying to understand why resetting to 0 is always good here. Wouldn't that mean we almost always want to call ToImmutableAndClear anyways?

Yes, it would probably always be ok. But it will incur an extra check in the cases where not needed. These are at least teh cases that it is a pure win always :)

@CyrusNajmabadi
Copy link
Copy Markdown
Contributor Author

Even if it's not the same length as the pool, it still resets to 0 and just calls ToImmutable causing the same additional array.

I think there's an argument to be made to remove ToImmutable (or rename it), and only have the ToImmutableAsClear as the primary interface here. That's the mainline case. THe case of wanting to convert to an array, but still have data in the builder seems... likely not helpful. but we also have 100x more cases of ArrayBuilder and ToImmutable, so i'm warier about such a large change.

@ryzngard
Copy link
Copy Markdown
Contributor

ryzngard commented Aug 2, 2022

Even if it's not the same length as the pool, it still resets to 0 and just calls ToImmutable causing the same additional array.

I think there's an argument to be made to remove ToImmutable (or rename it), and only have the ToImmutableAsClear as the primary interface here. That's the mainline case. THe case of wanting to convert to an array, but still have data in the builder seems... likely not helpful. but we also have 100x more cases of ArrayBuilder and ToImmutable, so i'm warier about such a large change.

Understood. I thought the main reason to want to use pooled arrays is because we can leave an array that is larger in memory and not have to allocate again. Might be something I'm missing here, since if we're not seeing benefits why do we use them at all? ToImmutableAndClear seems like it would be as performant as just using ImmutableArray.CreateBuilder.

@CyrusNajmabadi
Copy link
Copy Markdown
Contributor Author

ImmutableArray.CreateBuilder always ends up with some garbage as the builder itself becomes garbage. With ArrayBuilder+ToImmutableAndClear (and a known fixed-length) you always end up with a resultant ImmutableArray with no copying and no garbage (because the builder is pooled).

@CyrusNajmabadi CyrusNajmabadi merged commit 1dc2951 into dotnet:main Aug 2, 2022
@CyrusNajmabadi CyrusNajmabadi deleted the toImmutableAndClear branch August 2, 2022 05:46
@ghost ghost added this to the Next milestone Aug 2, 2022
@dibarbet dibarbet modified the milestones: Next, 17.4 P2 Sep 1, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants