Skip to content

Commit bc0f342

Browse files
authored
Reduce allocations in the C# lexer ctor (#76544)
* Attempt to reduce allocations in the C# lexer ctor The lexer ctor shows as about 0.5% of all allocations over the lifetime of the RoslynCodeAnalysisProcess in the csharp editing speedometer tests. There was already the concept of the LexerCache which had some pooling usage in it. This PR moves a couple other members to it to allow for their pooling.
1 parent 57ba28e commit bc0f342

File tree

3 files changed

+154
-28
lines changed

3 files changed

+154
-28
lines changed

src/Compilers/CSharp/Portable/Parser/Lexer.cs

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,10 @@ internal sealed partial class Lexer : AbstractLexer
8989
private DocumentationCommentParser? _xmlParser;
9090
private DirectiveParser? _directiveParser;
9191

92+
private SyntaxListBuilder _leadingTriviaCache;
93+
private SyntaxListBuilder _trailingTriviaCache;
94+
private SyntaxListBuilder? _directiveTriviaCache;
95+
9296
internal struct TokenInfo
9397
{
9498
// scanned values
@@ -116,11 +120,15 @@ public Lexer(SourceText text, CSharpParseOptions options, bool allowPreprocessor
116120
Debug.Assert(options != null);
117121

118122
_options = options;
119-
_builder = new StringBuilder();
120-
_identBuffer = new char[32];
121-
_cache = new LexerCache();
122123
_allowPreprocessorDirectives = allowPreprocessorDirectives;
123124
_interpolationFollowedByColon = interpolationFollowedByColon;
125+
126+
// Obtain pooled items
127+
_cache = LexerCache.GetInstance();
128+
_builder = _cache.StringBuilder;
129+
_identBuffer = _cache.IdentBuffer;
130+
_leadingTriviaCache = _cache.LeadingTriviaCache;
131+
_trailingTriviaCache = _cache.TrailingTriviaCache;
124132
}
125133

126134
public override void Dispose()
@@ -281,10 +289,6 @@ public SyntaxToken Lex(LexerMode mode)
281289
}
282290
}
283291

284-
private SyntaxListBuilder _leadingTriviaCache = new SyntaxListBuilder(10);
285-
private SyntaxListBuilder _trailingTriviaCache = new SyntaxListBuilder(10);
286-
private SyntaxListBuilder? _directiveTriviaCache;
287-
288292
private static int GetFullWidth(SyntaxListBuilder? builder)
289293
{
290294
int width = 0;

src/Compilers/CSharp/Portable/Parser/LexerCache.cs

Lines changed: 141 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,17 +5,16 @@
55
// #define COLLECT_STATS
66

77
using System;
8-
using Microsoft.CodeAnalysis.CSharp.Symbols;
9-
using Microsoft.CodeAnalysis.CSharp.Syntax;
108
using Microsoft.CodeAnalysis.PooledObjects;
11-
using Microsoft.CodeAnalysis.Text;
9+
using Microsoft.CodeAnalysis.Syntax.InternalSyntax;
1210
using Roslyn.Utilities;
13-
using System.Text;
1411

1512
namespace Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax
1613
{
17-
internal partial class LexerCache
14+
internal class LexerCache
1815
{
16+
private static readonly ObjectPool<LexerCache> s_lexerCachePool = new ObjectPool<LexerCache>(() => new LexerCache());
17+
1918
private static readonly ObjectPool<CachingIdentityFactory<string, SyntaxKind>> s_keywordKindPool =
2019
CachingIdentityFactory<string, SyntaxKind>.CreatePool(
2120
512,
@@ -30,23 +29,144 @@ internal partial class LexerCache
3029
return kind;
3130
});
3231

33-
private readonly TextKeyedCache<SyntaxTrivia> _triviaMap;
34-
private readonly TextKeyedCache<SyntaxToken> _tokenMap;
35-
private readonly CachingIdentityFactory<string, SyntaxKind> _keywordKindMap;
32+
private TextKeyedCache<SyntaxTrivia>? _triviaMap;
33+
private TextKeyedCache<SyntaxToken>? _tokenMap;
34+
private CachingIdentityFactory<string, SyntaxKind>? _keywordKindMap;
3635
internal const int MaxKeywordLength = 10;
3736

38-
internal LexerCache()
37+
private PooledStringBuilder? _stringBuilder;
38+
private readonly char[] _identBuffer;
39+
private SyntaxListBuilder? _leadingTriviaCache;
40+
private SyntaxListBuilder? _trailingTriviaCache;
41+
42+
private const int LeadingTriviaCacheInitialCapacity = 128;
43+
private const int TrailingTriviaCacheInitialCapacity = 16;
44+
45+
private LexerCache()
46+
{
47+
_identBuffer = new char[32];
48+
}
49+
50+
public static LexerCache GetInstance()
3951
{
40-
_triviaMap = TextKeyedCache<SyntaxTrivia>.GetInstance();
41-
_tokenMap = TextKeyedCache<SyntaxToken>.GetInstance();
42-
_keywordKindMap = s_keywordKindPool.Allocate();
52+
return s_lexerCachePool.Allocate();
4353
}
4454

45-
internal void Free()
55+
public void Free()
4656
{
47-
_keywordKindMap.Free();
48-
_triviaMap.Free();
49-
_tokenMap.Free();
57+
if (_keywordKindMap != null)
58+
{
59+
_keywordKindMap.Free();
60+
_keywordKindMap = null;
61+
}
62+
63+
if (_triviaMap != null)
64+
{
65+
_triviaMap.Free();
66+
_triviaMap = null;
67+
}
68+
69+
if (_tokenMap != null)
70+
{
71+
_tokenMap.Free();
72+
_tokenMap = null;
73+
}
74+
75+
if (_stringBuilder != null)
76+
{
77+
_stringBuilder.Free();
78+
_stringBuilder = null;
79+
}
80+
81+
if (_leadingTriviaCache != null)
82+
{
83+
if (_leadingTriviaCache.Capacity > LeadingTriviaCacheInitialCapacity * 2)
84+
{
85+
// Don't reuse _leadingTriviaCache if it has grown too large for pooling.
86+
_leadingTriviaCache = null;
87+
}
88+
else
89+
{
90+
_leadingTriviaCache.Clear();
91+
}
92+
}
93+
94+
if (_trailingTriviaCache != null)
95+
{
96+
if (_trailingTriviaCache.Capacity > TrailingTriviaCacheInitialCapacity * 2)
97+
{
98+
// Don't reuse _trailingTriviaCache if it has grown too large for pooling.
99+
_trailingTriviaCache = null;
100+
}
101+
else
102+
{
103+
_trailingTriviaCache.Clear();
104+
}
105+
}
106+
107+
s_lexerCachePool.Free(this);
108+
}
109+
110+
internal char[] IdentBuffer => _identBuffer;
111+
112+
private TextKeyedCache<SyntaxTrivia> TriviaMap
113+
{
114+
get
115+
{
116+
_triviaMap ??= TextKeyedCache<SyntaxTrivia>.GetInstance();
117+
118+
return _triviaMap;
119+
}
120+
}
121+
122+
private TextKeyedCache<SyntaxToken> TokenMap
123+
{
124+
get
125+
{
126+
_tokenMap ??= TextKeyedCache<SyntaxToken>.GetInstance();
127+
128+
return _tokenMap;
129+
}
130+
}
131+
132+
private CachingIdentityFactory<string, SyntaxKind> KeywordKindMap
133+
{
134+
get
135+
{
136+
_keywordKindMap ??= s_keywordKindPool.Allocate();
137+
138+
return _keywordKindMap;
139+
}
140+
}
141+
142+
internal PooledStringBuilder StringBuilder
143+
{
144+
get
145+
{
146+
_stringBuilder ??= PooledStringBuilder.GetInstance();
147+
148+
return _stringBuilder;
149+
}
150+
}
151+
152+
internal SyntaxListBuilder LeadingTriviaCache
153+
{
154+
get
155+
{
156+
_leadingTriviaCache ??= new SyntaxListBuilder(LeadingTriviaCacheInitialCapacity);
157+
158+
return _leadingTriviaCache;
159+
}
160+
}
161+
162+
internal SyntaxListBuilder TrailingTriviaCache
163+
{
164+
get
165+
{
166+
_trailingTriviaCache ??= new SyntaxListBuilder(TrailingTriviaCacheInitialCapacity);
167+
168+
return _trailingTriviaCache;
169+
}
50170
}
51171

52172
internal bool TryGetKeywordKind(string key, out SyntaxKind kind)
@@ -57,7 +177,7 @@ internal bool TryGetKeywordKind(string key, out SyntaxKind kind)
57177
return false;
58178
}
59179

60-
kind = _keywordKindMap.GetOrMakeValue(key);
180+
kind = KeywordKindMap.GetOrMakeValue(key);
61181
return kind != SyntaxKind.None;
62182
}
63183

@@ -69,12 +189,12 @@ internal SyntaxTrivia LookupTrivia<TArg>(
69189
Func<TArg, SyntaxTrivia> createTriviaFunction,
70190
TArg data)
71191
{
72-
var value = _triviaMap.FindItem(textBuffer, keyStart, keyLength, hashCode);
192+
var value = TriviaMap.FindItem(textBuffer, keyStart, keyLength, hashCode);
73193

74194
if (value == null)
75195
{
76196
value = createTriviaFunction(data);
77-
_triviaMap.AddItem(textBuffer, keyStart, keyLength, hashCode, value);
197+
TriviaMap.AddItem(textBuffer, keyStart, keyLength, hashCode, value);
78198
}
79199

80200
return value;
@@ -109,15 +229,15 @@ internal SyntaxToken LookupToken<TArg>(
109229
Func<TArg, SyntaxToken> createTokenFunction,
110230
TArg data)
111231
{
112-
var value = _tokenMap.FindItem(textBuffer, keyStart, keyLength, hashCode);
232+
var value = TokenMap.FindItem(textBuffer, keyStart, keyLength, hashCode);
113233

114234
if (value == null)
115235
{
116236
#if COLLECT_STATS
117237
Miss();
118238
#endif
119239
value = createTokenFunction(data);
120-
_tokenMap.AddItem(textBuffer, keyStart, keyLength, hashCode, value);
240+
TokenMap.AddItem(textBuffer, keyStart, keyLength, hashCode, value);
121241
}
122242
else
123243
{

src/Compilers/Core/Portable/Syntax/InternalSyntax/SyntaxListBuilder.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ internal class SyntaxListBuilder
1111
{
1212
private ArrayElement<GreenNode?>[] _nodes;
1313
public int Count { get; private set; }
14+
public int Capacity => _nodes.Length;
1415

1516
public SyntaxListBuilder(int size)
1617
{
@@ -24,6 +25,7 @@ public static SyntaxListBuilder Create()
2425

2526
public void Clear()
2627
{
28+
Array.Clear(_nodes, 0, _nodes.Length);
2729
this.Count = 0;
2830
}
2931

0 commit comments

Comments
 (0)