Skip to content

Commit 073cd0a

Browse files
authored
Merge pull request #22877 from dotnet/dev/toddgrun/MorePerfOptimizations
Dev/toddgrun/more perf optimizations
2 parents b13cb76 + 9cf611b commit 073cd0a

File tree

3 files changed

+92
-77
lines changed

3 files changed

+92
-77
lines changed

src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/CSharpTokenizer.cs

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -554,13 +554,16 @@ private StateResult VerbatimStringLiteral()
554554
return Transition(CSharpTokenizerState.Data, EndToken(SyntaxKind.StringLiteral));
555555
}
556556

557-
private StateResult QuotedCharacterLiteral() => QuotedLiteral('\'', SyntaxKind.CharacterLiteral);
557+
private StateResult QuotedCharacterLiteral() => QuotedLiteral('\'', IsEndQuotedCharacterLiteral, SyntaxKind.CharacterLiteral);
558558

559-
private StateResult QuotedStringLiteral() => QuotedLiteral('\"', SyntaxKind.StringLiteral);
559+
private StateResult QuotedStringLiteral() => QuotedLiteral('\"', IsEndQuotedStringLiteral, SyntaxKind.StringLiteral);
560560

561-
private StateResult QuotedLiteral(char quote, SyntaxKind literalType)
561+
private Func<char, bool> IsEndQuotedCharacterLiteral = (c) => c == '\\' || c == '\'' || ParserHelpers.IsNewLine(c);
562+
private Func<char, bool> IsEndQuotedStringLiteral = (c) => c == '\\' || c == '\"' || ParserHelpers.IsNewLine(c);
563+
564+
private StateResult QuotedLiteral(char quote, Func<char, bool> isEndQuotedLiteral, SyntaxKind literalType)
562565
{
563-
TakeUntil(c => c == '\\' || c == quote || ParserHelpers.IsNewLine(c));
566+
TakeUntil(isEndQuotedLiteral);
564567
if (CurrentCharacter == '\\')
565568
{
566569
TakeCurrent(); // Take the '\'

src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/LineTrackingStringBuffer.cs

Lines changed: 42 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@ internal class LineTrackingStringBuffer
1313
private readonly IList<TextLine> _lines;
1414
private readonly string _filePath;
1515
private TextLine _currentLine;
16-
private TextLine _endLine;
1716

1817
public LineTrackingStringBuffer(string content, string filePath)
1918
: this(content.ToCharArray(), filePath)
@@ -22,17 +21,16 @@ public LineTrackingStringBuffer(string content, string filePath)
2221

2322
public LineTrackingStringBuffer(char[] content, string filePath)
2423
{
25-
_endLine = new TextLine(0, 0);
26-
_lines = new List<TextLine>() { _endLine };
24+
_lines = new List<TextLine>();
2725

28-
Append(content);
26+
BuildTextLines(content);
2927

3028
_filePath = filePath;
3129
}
3230

3331
public int Length
3432
{
35-
get { return _endLine.End; }
33+
get { return _lines[_lines.Count - 1].End; }
3634
}
3735

3836
public SourceLocation EndLocation
@@ -43,46 +41,46 @@ public SourceLocation EndLocation
4341
public CharacterReference CharAt(int absoluteIndex)
4442
{
4543
var line = FindLine(absoluteIndex);
46-
if (line == null)
44+
if (line.IsDefault)
4745
{
4846
throw new ArgumentOutOfRangeException(nameof(absoluteIndex));
4947
}
5048
var idx = absoluteIndex - line.Start;
5149
return new CharacterReference(line.Content[idx], new SourceLocation(_filePath, absoluteIndex, line.Index, idx));
5250
}
5351

54-
private void Append(char[] content)
52+
private void BuildTextLines(char[] content)
5553
{
54+
string lineText;
55+
var lineStart = 0;
56+
5657
for (int i = 0; i < content.Length; i++)
5758
{
58-
AppendCore(content[i]);
59-
60-
// \r on it's own: Start a new line, otherwise wait for \n
61-
// Other Newline: Start a new line
62-
if ((content[i] == '\r' && (i + 1 == content.Length || content[i + 1] != '\n')) || (content[i] != '\r' && ParserHelpers.IsNewLine(content[i])))
59+
if (ParserHelpers.IsNewLine(content[i]))
6360
{
64-
PushNewLine();
61+
// \r on it's own: Start a new line, otherwise wait for \n
62+
// Other Newline: Start a new line
63+
if (content[i] == '\r' && i + 1 < content.Length && content[i + 1] == '\n')
64+
{
65+
i++;
66+
}
67+
68+
lineText = new string(content, lineStart, (i - lineStart) + 1); // +1 to include the current char
69+
_lines.Add(new TextLine(lineStart, _lines.Count, lineText));
70+
71+
lineStart = i + 1;
6572
}
6673
}
67-
}
6874

69-
private void PushNewLine()
70-
{
71-
_endLine = new TextLine(_endLine.End, _endLine.Index + 1);
72-
_lines.Add(_endLine);
73-
}
74-
75-
private void AppendCore(char chr)
76-
{
77-
Debug.Assert(_lines.Count > 0);
78-
_lines[_lines.Count - 1].Content.Append(chr);
75+
lineText = new string(content, lineStart, content.Length - lineStart); // no +1 as content.Length points past the last char already
76+
_lines.Add(new TextLine(lineStart, _lines.Count, lineText));
7977
}
8078

8179
private TextLine FindLine(int absoluteIndex)
8280
{
83-
TextLine selected = null;
81+
TextLine selected;
8482

85-
if (_currentLine == null)
83+
if (_currentLine.IsDefault)
8684
{
8785
// Scan from line 0
8886
selected = ScanLines(absoluteIndex, 0, _lines.Count);
@@ -104,6 +102,10 @@ private TextLine FindLine(int absoluteIndex)
104102
selected = ScanLines(absoluteIndex, _currentLine.Index, _lines.Count);
105103
}
106104
}
105+
else
106+
{
107+
selected = default;
108+
}
107109
}
108110
else if (absoluteIndex < _currentLine.Start)
109111
{
@@ -122,14 +124,18 @@ private TextLine FindLine(int absoluteIndex)
122124
selected = ScanLines(absoluteIndex, 0, _currentLine.Index);
123125
}
124126
}
127+
else
128+
{
129+
selected = default;
130+
}
125131
}
126132
else
127133
{
128134
// This index is on the last read line
129135
selected = _currentLine;
130136
}
131137

132-
Debug.Assert(selected == null || selected.Contains(absoluteIndex));
138+
Debug.Assert(selected.IsDefault || selected.Contains(absoluteIndex));
133139
_currentLine = selected;
134140
return selected;
135141
}
@@ -159,7 +165,7 @@ private TextLine ScanLines(int absoluteIndex, int startLineIndex, int endLineInd
159165
}
160166
}
161167

162-
return null;
168+
return default;
163169
}
164170

165171
internal struct CharacterReference
@@ -175,28 +181,26 @@ public CharacterReference(char character, SourceLocation location)
175181
public SourceLocation Location { get; }
176182
}
177183

178-
private class TextLine
184+
private struct TextLine
179185
{
180-
private StringBuilder _content = new StringBuilder();
181-
182-
public TextLine(int start, int index)
186+
public TextLine(int start, int index, string content)
183187
{
184188
Start = start;
185189
Index = index;
190+
Content = content;
186191
}
187192

188-
public StringBuilder Content
189-
{
190-
get { return _content; }
191-
}
193+
public string Content { get; }
194+
195+
public bool IsDefault => Content == null;
192196

193197
public int Length
194198
{
195199
get { return Content.Length; }
196200
}
197201

198-
public int Start { get; set; }
199-
public int Index { get; set; }
202+
public int Start { get; }
203+
public int Index { get; }
200204

201205
public int End
202206
{

src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/TokenizerBackedParser.cs

Lines changed: 43 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -16,25 +16,25 @@ internal abstract class TokenizerBackedParser<TTokenizer> : ParserBase
1616
private readonly TokenizerView<TTokenizer> _tokenizer;
1717
private SyntaxListBuilder<SyntaxToken>? _tokenBuilder;
1818

19-
// Following four high traffic methods cached as using method groups would cause allocation on every invocation.
20-
protected static readonly Func<SyntaxToken, bool> IsSpacingToken = (token) =>
21-
{
22-
return token.Kind == SyntaxKind.Whitespace;
23-
};
24-
25-
protected static readonly Func<SyntaxToken, bool> IsSpacingTokenIncludingNewLines = (token) =>
26-
{
27-
return IsSpacingToken(token) || token.Kind == SyntaxKind.NewLine;
28-
};
29-
30-
protected static readonly Func<SyntaxToken, bool> IsSpacingTokenIncludingComments = (token) =>
31-
{
32-
return IsSpacingToken(token) || token.Kind == SyntaxKind.CSharpComment;
33-
};
34-
35-
protected static readonly Func<SyntaxToken, bool> IsSpacingTokenIncludingNewLinesAndComments = (token) =>
36-
{
37-
return IsSpacingTokenIncludingNewLines(token) || token.Kind == SyntaxKind.CSharpComment;
19+
// Following four high traffic methods cached as using method groups would cause allocation on every invocation.
20+
protected static readonly Func<SyntaxToken, bool> IsSpacingToken = (token) =>
21+
{
22+
return token.Kind == SyntaxKind.Whitespace;
23+
};
24+
25+
protected static readonly Func<SyntaxToken, bool> IsSpacingTokenIncludingNewLines = (token) =>
26+
{
27+
return IsSpacingToken(token) || token.Kind == SyntaxKind.NewLine;
28+
};
29+
30+
protected static readonly Func<SyntaxToken, bool> IsSpacingTokenIncludingComments = (token) =>
31+
{
32+
return IsSpacingToken(token) || token.Kind == SyntaxKind.CSharpComment;
33+
};
34+
35+
protected static readonly Func<SyntaxToken, bool> IsSpacingTokenIncludingNewLinesAndComments = (token) =>
36+
{
37+
return IsSpacingTokenIncludingNewLines(token) || token.Kind == SyntaxKind.CSharpComment;
3838
};
3939

4040
protected TokenizerBackedParser(LanguageCharacteristics<TTokenizer> language, ParserContext context)
@@ -224,7 +224,19 @@ protected internal void PutCurrentBack()
224224

225225
protected internal bool NextIs(SyntaxKind type)
226226
{
227-
return NextIs(token => token != null && type == token.Kind);
227+
// Duplicated logic with NextIs(Func...) to prevent allocation
228+
var cur = CurrentToken;
229+
var result = false;
230+
if (NextToken())
231+
{
232+
result = (type == CurrentToken.Kind);
233+
PutCurrentBack();
234+
}
235+
236+
PutBack(cur);
237+
EnsureCurrent();
238+
239+
return result;
228240
}
229241

230242
protected internal bool NextIs(params SyntaxKind[] types)
@@ -235,21 +247,17 @@ protected internal bool NextIs(params SyntaxKind[] types)
235247
protected internal bool NextIs(Func<SyntaxToken, bool> condition)
236248
{
237249
var cur = CurrentToken;
250+
var result = false;
238251
if (NextToken())
239252
{
240-
var result = condition(CurrentToken);
253+
result = condition(CurrentToken);
241254
PutCurrentBack();
242-
PutBack(cur);
243-
EnsureCurrent();
244-
return result;
245-
}
246-
else
247-
{
248-
PutBack(cur);
249-
EnsureCurrent();
250255
}
251256

252-
return false;
257+
PutBack(cur);
258+
EnsureCurrent();
259+
260+
return result;
253261
}
254262

255263
protected internal bool Was(SyntaxKind type)
@@ -289,11 +297,11 @@ protected bool EnsureCurrent()
289297

290298
protected internal IReadOnlyList<SyntaxToken> ReadWhile(Func<SyntaxToken, bool> condition)
291299
{
292-
if (!EnsureCurrent() || !condition(CurrentToken))
293-
{
294-
return Array.Empty<SyntaxToken>();
295-
}
296-
300+
if (!EnsureCurrent() || !condition(CurrentToken))
301+
{
302+
return Array.Empty<SyntaxToken>();
303+
}
304+
297305
var result = new List<SyntaxToken>();
298306
do
299307
{

0 commit comments

Comments
 (0)