-
Notifications
You must be signed in to change notification settings - Fork 191
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
Reduce allocations in Razor's DirectiveVisitor #10537
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -69,6 +69,8 @@ protected override void ExecuteCore(RazorCodeDocument codeDocument) | |
var context = TagHelperDocumentContext.Create(tagHelperPrefix, matches.ToImmutableArray()); | ||
codeDocument.SetTagHelperContext(context); | ||
codeDocument.SetPreTagHelperSyntaxTree(syntaxTree); | ||
|
||
visitor.Dispose(); | ||
} | ||
|
||
private static bool MatchesDirective(TagHelperDescriptor descriptor, string typePattern, string assemblyName) | ||
|
@@ -106,32 +108,34 @@ private static ReadOnlySpan<char> RemoveGlobalPrefix(in ReadOnlySpan<char> span) | |
return span; | ||
} | ||
|
||
internal abstract class DirectiveVisitor(HashSet<TagHelperDescriptor> matches) : SyntaxWalker | ||
internal abstract class DirectiveVisitor(HashSet<TagHelperDescriptor> matches) : SyntaxWalker, IDisposable | ||
{ | ||
protected readonly HashSet<TagHelperDescriptor> Matches = matches; | ||
|
||
public abstract string TagHelperPrefix { get; } | ||
|
||
public abstract void Visit(RazorSyntaxTree tree); | ||
|
||
public abstract void Dispose(); | ||
} | ||
|
||
internal sealed class TagHelperDirectiveVisitor : DirectiveVisitor | ||
{ | ||
private readonly List<TagHelperDescriptor> _tagHelpers; | ||
private readonly PooledObject<ImmutableArray<TagHelperDescriptor>.Builder> _tagHelpers; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Random thought: Is ImmutableArray the best choice here? Since this builder is never realized, a pooled list might be just as suitable, but a bit simpler, especially if an initial capacity can be set? Looks like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or even just a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I tried PooledArrayBuilder initially, and it didn't seem to work because I was trying to store that into a field, and that type is defined as a NonCopyable struct. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I didn't go with PooledList as I talked to Andrew and we weren't sure of it's benefit. It's a struct, but not marked as non-copyable, so it could be used, but it doesn't seem to be used a lot throughout the codebase. I'm flexible, and can go with that (or whatever else works) if you'd prefer. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, (sorry, my bad when i said "pooled list" and wasn't specific. I knew we had one that was a ref struct, but couldn't remember which) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, this feels cleaner. Thanks for the suggestions! |
||
private string _tagHelperPrefix; | ||
|
||
public TagHelperDirectiveVisitor(IReadOnlyList<TagHelperDescriptor> tagHelpers, HashSet<TagHelperDescriptor> matches) | ||
: base(matches) | ||
{ | ||
// We don't want to consider components in a view document. | ||
_tagHelpers = new(); | ||
_tagHelpers = ArrayBuilderPool<TagHelperDescriptor>.GetPooledObject(); | ||
|
||
for (var i = 0; i < tagHelpers.Count; i++) | ||
{ | ||
var tagHelper = tagHelpers[i]; | ||
if (!tagHelper.IsAnyComponentDocumentTagHelper()) | ||
{ | ||
_tagHelpers.Add(tagHelper); | ||
_tagHelpers.Object.Add(tagHelper); | ||
} | ||
} | ||
} | ||
|
@@ -165,13 +169,13 @@ public override void VisitRazorDirective(RazorDirectiveSyntax node) | |
continue; | ||
} | ||
|
||
if (!AssemblyContainsTagHelpers(addTagHelper.AssemblyName, _tagHelpers)) | ||
if (!AssemblyContainsTagHelpers(addTagHelper.AssemblyName, _tagHelpers.Object)) | ||
{ | ||
// No tag helpers in the assembly. | ||
continue; | ||
} | ||
|
||
foreach (var tagHelper in _tagHelpers) | ||
foreach (var tagHelper in _tagHelpers.Object) | ||
{ | ||
if (MatchesDirective(tagHelper, addTagHelper.TypePattern, addTagHelper.AssemblyName)) | ||
{ | ||
|
@@ -189,13 +193,13 @@ public override void VisitRazorDirective(RazorDirectiveSyntax node) | |
} | ||
|
||
|
||
if (!AssemblyContainsTagHelpers(removeTagHelper.AssemblyName, _tagHelpers)) | ||
if (!AssemblyContainsTagHelpers(removeTagHelper.AssemblyName, _tagHelpers.Object)) | ||
{ | ||
// No tag helpers in the assembly. | ||
continue; | ||
} | ||
|
||
foreach (var tagHelper in _tagHelpers) | ||
foreach (var tagHelper in _tagHelpers.Object) | ||
{ | ||
if (MatchesDirective(tagHelper, removeTagHelper.TypePattern, removeTagHelper.AssemblyName)) | ||
{ | ||
|
@@ -217,7 +221,7 @@ public override void VisitRazorDirective(RazorDirectiveSyntax node) | |
} | ||
} | ||
|
||
private static bool AssemblyContainsTagHelpers(string assemblyName, List<TagHelperDescriptor> tagHelpers) | ||
private static bool AssemblyContainsTagHelpers(string assemblyName, ImmutableArray<TagHelperDescriptor>.Builder tagHelpers) | ||
{ | ||
foreach (var tagHelper in tagHelpers) | ||
{ | ||
|
@@ -229,11 +233,16 @@ private static bool AssemblyContainsTagHelpers(string assemblyName, List<TagHelp | |
|
||
return false; | ||
} | ||
|
||
public override void Dispose() | ||
{ | ||
_tagHelpers.Dispose(); | ||
} | ||
} | ||
|
||
internal sealed class ComponentDirectiveVisitor : DirectiveVisitor | ||
{ | ||
private readonly ImmutableArray<TagHelperDescriptor> _notFullyQualifiedComponents; | ||
private readonly PooledObject<ImmutableArray<TagHelperDescriptor>.Builder> _notFullyQualifiedComponents; | ||
private readonly string _filePath; | ||
private RazorSourceDocument _source; | ||
|
||
|
@@ -242,7 +251,7 @@ public ComponentDirectiveVisitor(string filePath, IReadOnlyList<TagHelperDescrip | |
{ | ||
_filePath = filePath; | ||
|
||
using var builder = new PooledArrayBuilder<TagHelperDescriptor>(capacity: tagHelpers.Count); | ||
_notFullyQualifiedComponents = ArrayBuilderPool<TagHelperDescriptor>.GetPooledObject(); | ||
|
||
for (var i = 0; i < tagHelpers.Count; i++) | ||
{ | ||
|
@@ -260,7 +269,7 @@ public ComponentDirectiveVisitor(string filePath, IReadOnlyList<TagHelperDescrip | |
continue; | ||
} | ||
|
||
builder.Add(tagHelper); | ||
_notFullyQualifiedComponents.Object.Add(tagHelper); | ||
|
||
if (currentNamespace is null) | ||
{ | ||
|
@@ -282,8 +291,6 @@ public ComponentDirectiveVisitor(string filePath, IReadOnlyList<TagHelperDescrip | |
Matches.Add(tagHelper); | ||
} | ||
} | ||
|
||
_notFullyQualifiedComponents = builder.DrainToImmutable(); | ||
} | ||
|
||
// There is no support for tag helper prefix in component documents. | ||
|
@@ -343,7 +350,7 @@ public override void VisitRazorDirective(RazorDirectiveSyntax node) | |
continue; | ||
} | ||
|
||
foreach (var tagHelper in _notFullyQualifiedComponents) | ||
foreach (var tagHelper in _notFullyQualifiedComponents.Object) | ||
{ | ||
Debug.Assert(!tagHelper.IsComponentFullyQualifiedNameMatch, "We've already processed these."); | ||
|
||
|
@@ -427,5 +434,10 @@ internal static bool IsTagHelperFromMangledClass(TagHelperDescriptor tagHelper) | |
{ | ||
return ComponentMetadata.IsMangledClass(tagHelper.GetTypeNameIdentifier()); | ||
} | ||
|
||
public override void Dispose() | ||
{ | ||
_notFullyQualifiedComponents.Dispose(); | ||
} | ||
} | ||
} |
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.
Consider
using (visitor) {
on line 55-ish, and ending it here, so its clearer to everyone that its being disposed, and so that there is a try..finally in placeThere 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 can change to that, I didn't go that route initially as it didn't fall directly into the using pattern and it's really not a big deal if the disposal doesn't end up happening.
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.
Is it not? I would have thought that would mean the pool allocates lots of new builders, defeating the purpose of the PR.
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.
Let me rephrase, it's not a big deal if it's an exceptional case where this occurs, not if it's the normal code path (which would probably indicate bigger problems than avoiding these allocations)