-
Notifications
You must be signed in to change notification settings - Fork 199
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
Use pooled objects in more locations #10598
Conversation
@@ -38,7 +39,7 @@ public static async Task<TextEdit[]> GetUsingStatementEditsAsync(RazorCodeDocume | |||
var oldUsings = await FindUsingDirectiveStringsAsync(originalCSharpText, cancellationToken).ConfigureAwait(false); | |||
var newUsings = await FindUsingDirectiveStringsAsync(changedCSharpText, cancellationToken).ConfigureAwait(false); | |||
|
|||
var edits = new List<TextEdit>(); | |||
using var _ = ListPool<TextEdit>.GetPooledObject(out var edits); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm unsure which would be better here, ListPool
or ArrayBuilderPool
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ListPool<T>
is fine, but if you just want an array in the end, consider using PooledArrayBuilder<T>
like so:
using var edits = new PooledArrayBuilder<TextEdit>();
For small arrays (4 or fewer elements), it uses stack space. For larger arrays, it grabs a builder from ArrayBuilderPool<T>
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be read in the least authoratative voice you can imagine, but I've always been of the opinion that if something is never realized as an immutable collection, then using an immutable collection builder is a bit silly, and List is fine. I think they work the same way (resizing internal arrays), but my hope is that List, being the more common type, might get some tiny jit advantage.
EDIT: Ignore me, Dustin knows best!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be fair, you're completley right. ListPool<T>
is fine when you're just going to call ToArray()
. However, if it's likely to be a small array, PooledArrayBuilder<T>
we'll be better because it won't even hit the pool.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm much happier when there is one specific guidance we can give. So "just use pooled array builder, and call builder.toimmutable" is an easy rule to remember.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I've arrived at for this change
- Default to
PooledArrayBuilder<T>
for places that are just building them and thenToArray/ToImmutable
- Use
ArrayBuilderPool<T>
when there's lots of functions passing around the list but one cental start/end of the chain to manage lifetime and are synchronous (useAsRef
) - Use
ListPool<T>
for the async version of above
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Use ArrayBuilderPool when there's lots of functions passing around the list but one cental start/end of the chain to manage lifetime and are synchronous (use AsRef)
- Use ListPool for the async version of above
FWIW, ArrayBuilderPool<T>
can be used for both of those situations. ImmutableArray<T>.Builder
is a reference type and does not need to be passed by ref. Of course, PooledArrayBuilder<T>
can't be passed to an async method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the last and most nit-picky way, I just find it more annoying to write ImmutableArray<T>.Builder
as the type. If there's a perf benefit I'm more than happy to amend my thinking, otherwise I'm lazy and like typing less
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or better put, I should have merged the first two dots
PooledArrayBuilder<T>
for all cases, and if passed sync pass by ref.ListPool<T>
for passing to async methods
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just find it more annoying to write
ImmutableArray<T>.Builder
as the type
For sure. I totally agree with you on that. 😄
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry to "request changes", but there are a few spots that'll cause problems if this goes in. In general, I have a few pieces of feedback that apply throughout:
- Prefer
PooledArrayBuilder<T>
when the goal is to create anImmutableArray<T>
or aT[]
. This data structure is generally more efficient because it stores the first four elements on the stack and only acquires anImmutableArray<T>.Builder
fromArrayBuilderPool<T>
if there are more than four elements. For a completely temporary array (i.e. collecting a few items and just iterating them),PooledArrayBuilder<T>
is especially useful. Also,PooledArrayBuider<T>
is better ergonomically, since it doesn't require anout var
. - Call
ImmutableArray<T>.Builder.ToImmutable()
rather thanToImmutableArray()
.ToImmutable()
is the instance method provided on all immutable collection builder types to produce the final collection.ToImmutableArray()
is an extension method provided for converting other data structures to anImmutableArray<T>
. Ultimately,ToImmutableArray()
will work, but it ends up making an unnecessary extra null check. - Don't spread builders into collection expressions. The compiler does not generate optimal code for this scenario. Instead, call
ToImmutable()
orToArray()
. - Be sure to set the capacity when known. I spotted one place where the capacity was no longer set. For many structures, we have
SetCapacityIflarger(...)
extension methods.PooledArrayBuilder<T>
can take a capacity in its constructor. - Do not allow pooled objects to escape from methods! This is very dangerous, and I marked a couple of spots where this was happening. When this happens to a pooled object retrieved in a
using
, the object will still be used outside the method after it has been returned to the pool.
@@ -38,7 +39,7 @@ public static async Task<TextEdit[]> GetUsingStatementEditsAsync(RazorCodeDocume | |||
var oldUsings = await FindUsingDirectiveStringsAsync(originalCSharpText, cancellationToken).ConfigureAwait(false); | |||
var newUsings = await FindUsingDirectiveStringsAsync(changedCSharpText, cancellationToken).ConfigureAwait(false); | |||
|
|||
var edits = new List<TextEdit>(); | |||
using var _ = ListPool<TextEdit>.GetPooledObject(out var edits); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ListPool<T>
is fine, but if you just want an array in the end, consider using PooledArrayBuilder<T>
like so:
using var edits = new PooledArrayBuilder<TextEdit>();
For small arrays (4 or fewer elements), it uses stack space. For larger arrays, it grabs a builder from ArrayBuilderPool<T>
.
...rosoft.AspNetCore.Razor.LanguageServer/CodeActions/CSharp/DefaultCSharpCodeActionProvider.cs
Outdated
Show resolved
Hide resolved
...rosoft.AspNetCore.Razor.LanguageServer/CodeActions/CSharp/DefaultCSharpCodeActionProvider.cs
Outdated
Show resolved
Hide resolved
...ft.AspNetCore.Razor.LanguageServer/CodeActions/CSharp/TypeAccessibilityCodeActionProvider.cs
Outdated
Show resolved
Hide resolved
...ft.AspNetCore.Razor.LanguageServer/CodeActions/CSharp/TypeAccessibilityCodeActionProvider.cs
Outdated
Show resolved
Hide resolved
...re.Razor.LanguageServer/DocumentPresentation/AbstractTextDocumentPresentationEndpointBase.cs
Outdated
Show resolved
Hide resolved
src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/Formatting/CSharpFormatter.cs
Outdated
Show resolved
Hide resolved
src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/MapCode/MapCodeEndpoint.cs
Outdated
Show resolved
Hide resolved
src/Razor/src/Microsoft.VisualStudio.LegacyEditor.Razor/Parsing/VisualStudioRazorParser.cs
Outdated
Show resolved
Hide resolved
src/Razor/src/Microsoft.VisualStudio.LegacyEditor.Razor/Parsing/VisualStudioRazorParser.cs
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thoughts:
- Legacy editor change is scary
I never know whether to callI think Dustin answered this above.ToImmutableArray()
or.DrainToImmutable()
Our use of ListPool vs ArrayBuilderPool is inconsistent but I don't know if thats an issue or notI think Dustin answered this above
Overall a very good change and cleanup though, thank you
.../src/Microsoft.AspNetCore.Razor.LanguageServer/Completion/AggregateCompletionItemResolver.cs
Outdated
Show resolved
Hide resolved
src/Razor/src/Microsoft.VisualStudio.LegacyEditor.Razor/Parsing/VisualStudioRazorParser.cs
Outdated
Show resolved
Hide resolved
...Razor/src/Microsoft.VisualStudio.RazorExtension/SyntaxVisualizer/IntraTextAdornmentTagger.cs
Show resolved
Hide resolved
Converting to draft until I've responded/fixed feedback. Going to do it incrementally |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is looking much better! Happy to approve this change. However, be mindful of the loop reversal in VisualStudioRazorParser
. Please don't merge without addressing that.
...rosoft.AspNetCore.Razor.LanguageServer/CodeActions/CSharp/DefaultCSharpCodeActionProvider.cs
Show resolved
Hide resolved
...ft.AspNetCore.Razor.LanguageServer/CodeActions/CSharp/TypeAccessibilityCodeActionProvider.cs
Show resolved
Hide resolved
...ft.AspNetCore.Razor.LanguageServer/CodeActions/CSharp/TypeAccessibilityCodeActionProvider.cs
Outdated
Show resolved
Hide resolved
...ft.AspNetCore.Razor.LanguageServer/CodeActions/CSharp/TypeAccessibilityCodeActionProvider.cs
Outdated
Show resolved
Hide resolved
...Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/CodeActions/CodeActionResolveEndpoint.cs
Show resolved
Hide resolved
...spNetCore.Razor.LanguageServer/CodeActions/Razor/ComponentAccessibilityCodeActionProvider.cs
Outdated
Show resolved
Hide resolved
...re.Razor.LanguageServer/DocumentPresentation/AbstractTextDocumentPresentationEndpointBase.cs
Outdated
Show resolved
Hide resolved
@@ -77,7 +77,7 @@ public void ApplyCapabilities(VSInternalServerCapabilities serverCapabilities, V | |||
|
|||
private async Task<WorkspaceEdit?> HandleMappingsAsync(VSInternalMapCodeMapping[] mappings, Guid mapCodeCorrelationId, CancellationToken cancellationToken) | |||
{ | |||
using var _ = ArrayBuilderPool<TextDocumentEdit>.GetPooledObject(out var changes); | |||
using var _ = ListPool<TextDocumentEdit>.GetPooledObject(out var changes); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
List<>
is good here. You could also use a PooledArrayBuilder<T>
and pass it by ref as I described above.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is used in TryMapCodeAsync
so I think it has to be a non-ref argument
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes -- good point.
src/Razor/src/Microsoft.AspNetCore.Razor.LanguageServer/MapCode/MapCodeEndpoint.cs
Outdated
Show resolved
Hide resolved
@@ -38,7 +39,7 @@ public static async Task<TextEdit[]> GetUsingStatementEditsAsync(RazorCodeDocume | |||
var oldUsings = await FindUsingDirectiveStringsAsync(originalCSharpText, cancellationToken).ConfigureAwait(false); | |||
var newUsings = await FindUsingDirectiveStringsAsync(changedCSharpText, cancellationToken).ConfigureAwait(false); | |||
|
|||
var edits = new List<TextEdit>(); | |||
using var _ = ListPool<TextEdit>.GetPooledObject(out var edits); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Use ArrayBuilderPool when there's lots of functions passing around the list but one cental start/end of the chain to manage lifetime and are synchronous (use AsRef)
- Use ListPool for the async version of above
FWIW, ArrayBuilderPool<T>
can be used for both of those situations. ImmutableArray<T>.Builder
is a reference type and does not need to be passed by ref. Of course, PooledArrayBuilder<T>
can't be passed to an async method.
{ | ||
var typeAccessibilityCodeActions = new List<RazorVSInternalCodeAction>(1); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@DustinCampbell this had a list set to 1 and I'm not really sure why... I think I wouldn't need to do that here because that would just keep it on the stack?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Super weird! I would just ignore the capacity in that case.
Ran an integration test just to be sure: https://devdiv.visualstudio.com/DevDiv/_build/results?buildId=9862176&view=results |
Cleaning up some places that are newing up Lists intead of using the object pool. I also moved a few things to ImmutableArray instead of List since they weren't mutated after initial collection