Skip to content

Commit 9e605b3

Browse files
authored
Create a specialized pool for arrays of ClassifiedSpanInternal (#11925)
Create a specialized pool for arrays of ClassifiedSpanInternal These arrays show up as a fairly large allocation in the typing scenario of the razor speedometer test. There are 150 MB (2.1%)of allocations of ClassifiedSpanInternal[] in the profile, but only 2.1 MB of ImmutableArray<ClassifiedSpanInternal>. This should be partly addressed by the earlier switch from DrainToImmutable use ToImmutableAndClear. However, this is also indicative that the 512 entry threshold for ArrayBuilderPool isn't sufficient for ClassifiedSpanInternal array counts. Roslyn has experienced this too and utilizes separate array pools for classification/tagging performance. The new pool has a threshold of 16K, much larger than the 512 entry limit. I tested this against a 60 KB razor file and was hitting about 7K entries, so this seemed like a pretty reasonable value. To ensure this doesn't add to total memory usage, the pool is limited to only 5 entries, as cconcurrent execution of classification isn't common. ![image](https://github.com/user-attachments/assets/0fa28ed4-816e-480e-8fa3-bd8871e0e986) ... ![image](https://github.com/user-attachments/assets/45cffb1a-e93b-43e3-9768-2a478c46cda7)
2 parents 3484948 + 3f5084c commit 9e605b3

File tree

1 file changed

+32
-1
lines changed

1 file changed

+32
-1
lines changed

src/Compiler/Microsoft.CodeAnalysis.Razor.Compiler/src/Language/ClassifiedSpanVisitor.cs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,14 @@
66
using Microsoft.AspNetCore.Razor.Language.Legacy;
77
using Microsoft.AspNetCore.Razor.Language.Syntax;
88
using Microsoft.AspNetCore.Razor.PooledObjects;
9+
using Microsoft.Extensions.ObjectPool;
910

1011
namespace Microsoft.AspNetCore.Razor.Language;
1112

1213
internal class ClassifiedSpanVisitor : SyntaxWalker
1314
{
15+
private static readonly ObjectPool<ImmutableArray<ClassifiedSpanInternal>.Builder> Pool = DefaultPool.Create(Policy.Instance, size: 5);
16+
1417
private readonly RazorSourceDocument _source;
1518
private readonly ImmutableArray<ClassifiedSpanInternal>.Builder _spans;
1619

@@ -51,7 +54,7 @@ private ClassifiedSpanVisitor(RazorSourceDocument source, ImmutableArray<Classif
5154

5255
public static ImmutableArray<ClassifiedSpanInternal> VisitRoot(RazorSyntaxTree syntaxTree)
5356
{
54-
using var _ = ArrayBuilderPool<ClassifiedSpanInternal>.GetPooledObject(out var builder);
57+
using var _ = Pool.GetPooledObject(out var builder);
5558

5659
var visitor = new ClassifiedSpanVisitor(syntaxTree.Source, builder);
5760
visitor.Visit(syntaxTree.Root);
@@ -363,4 +366,32 @@ private void WriteSpan(SyntaxToken token, SpanKindInternal kind, AcceptedCharact
363366
var span = new ClassifiedSpanInternal(spanSource, blockSource, kind, _currentBlockKind, acceptedCharacters.Value);
364367
_spans.Add(span);
365368
}
369+
370+
private sealed class Policy : IPooledObjectPolicy<ImmutableArray<ClassifiedSpanInternal>.Builder>
371+
{
372+
public static readonly Policy Instance = new();
373+
374+
// Significantly larger than DefaultPool.MaximumObjectSize as there shouldn't be much concurrency
375+
// of these arrays (we limit the number of pooled items to 5) and they are commonly large
376+
public const int MaximumObjectSize = DefaultPool.MaximumObjectSize * 32;
377+
378+
private Policy()
379+
{
380+
}
381+
382+
public ImmutableArray<ClassifiedSpanInternal>.Builder Create() => ImmutableArray.CreateBuilder<ClassifiedSpanInternal>();
383+
384+
public bool Return(ImmutableArray<ClassifiedSpanInternal>.Builder builder)
385+
{
386+
builder.Clear();
387+
388+
if (builder.Capacity > MaximumObjectSize)
389+
{
390+
// Differs from ArrayBuilderPool.Policy's behavior as we allow our array to grow significantly larger
391+
builder.Capacity = 0;
392+
}
393+
394+
return true;
395+
}
396+
}
366397
}

0 commit comments

Comments
 (0)