A couple more perf optimizations focused around memory allocations.
This is the last of the easy wins that I could find for typing in large razor files (minus the logged bug to move the divergence checker code off the UI thread). In the profile for the large document editing, these changes reduce allocated memory during RazorSyntaxTree.Parse by about 25%. CPU wise, the win isn't quite as dramatic, only a couple percent improvement under RazorSyntaxTree.Parse.
This commit is contained in:
parent
3a9368b26b
commit
904fd19605
|
|
@ -554,13 +554,16 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
return Transition(CSharpTokenizerState.Data, EndToken(SyntaxKind.StringLiteral));
|
return Transition(CSharpTokenizerState.Data, EndToken(SyntaxKind.StringLiteral));
|
||||||
}
|
}
|
||||||
|
|
||||||
private StateResult QuotedCharacterLiteral() => QuotedLiteral('\'', SyntaxKind.CharacterLiteral);
|
private StateResult QuotedCharacterLiteral() => QuotedLiteral('\'', IsEndQuotedCharacterLiteral, SyntaxKind.CharacterLiteral);
|
||||||
|
|
||||||
private StateResult QuotedStringLiteral() => QuotedLiteral('\"', SyntaxKind.StringLiteral);
|
private StateResult QuotedStringLiteral() => QuotedLiteral('\"', IsEndQuotedStringLiteral, SyntaxKind.StringLiteral);
|
||||||
|
|
||||||
private StateResult QuotedLiteral(char quote, SyntaxKind literalType)
|
private Func<char, bool> IsEndQuotedCharacterLiteral = (c) => c == '\\' || c == '\'' || ParserHelpers.IsNewLine(c);
|
||||||
|
private Func<char, bool> IsEndQuotedStringLiteral = (c) => c == '\\' || c == '\"' || ParserHelpers.IsNewLine(c);
|
||||||
|
|
||||||
|
private StateResult QuotedLiteral(char quote, Func<char, bool> isEndQuotedLiteral, SyntaxKind literalType)
|
||||||
{
|
{
|
||||||
TakeUntil(c => c == '\\' || c == quote || ParserHelpers.IsNewLine(c));
|
TakeUntil(isEndQuotedLiteral);
|
||||||
if (CurrentCharacter == '\\')
|
if (CurrentCharacter == '\\')
|
||||||
{
|
{
|
||||||
TakeCurrent(); // Take the '\'
|
TakeCurrent(); // Take the '\'
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
private readonly IList<TextLine> _lines;
|
private readonly IList<TextLine> _lines;
|
||||||
private readonly string _filePath;
|
private readonly string _filePath;
|
||||||
private TextLine _currentLine;
|
private TextLine _currentLine;
|
||||||
private TextLine _endLine;
|
|
||||||
|
|
||||||
public LineTrackingStringBuffer(string content, string filePath)
|
public LineTrackingStringBuffer(string content, string filePath)
|
||||||
: this(content.ToCharArray(), filePath)
|
: this(content.ToCharArray(), filePath)
|
||||||
|
|
@ -22,28 +21,27 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
|
|
||||||
public LineTrackingStringBuffer(char[] content, string filePath)
|
public LineTrackingStringBuffer(char[] content, string filePath)
|
||||||
{
|
{
|
||||||
_endLine = new TextLine(0, 0);
|
_lines = new List<TextLine>();
|
||||||
_lines = new List<TextLine>() { _endLine };
|
|
||||||
|
|
||||||
Append(content);
|
BuildTextLines(content);
|
||||||
|
|
||||||
_filePath = filePath;
|
_filePath = filePath;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Length
|
public int Length
|
||||||
{
|
{
|
||||||
get { return _endLine.End; }
|
get { return _lines[_lines.Count - 1].End; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public SourceLocation EndLocation
|
public SourceLocation EndLocation
|
||||||
{
|
{
|
||||||
get { return new SourceLocation(_filePath, Length, _lines.Count - 1, _lines[_lines.Count - 1].Length); }
|
get { return new SourceLocation(_filePath, Length, _lines.Count - 1, Length); }
|
||||||
}
|
}
|
||||||
|
|
||||||
public CharacterReference CharAt(int absoluteIndex)
|
public CharacterReference CharAt(int absoluteIndex)
|
||||||
{
|
{
|
||||||
var line = FindLine(absoluteIndex);
|
var line = FindLine(absoluteIndex);
|
||||||
if (line == null)
|
if (line.IsDefault)
|
||||||
{
|
{
|
||||||
throw new ArgumentOutOfRangeException(nameof(absoluteIndex));
|
throw new ArgumentOutOfRangeException(nameof(absoluteIndex));
|
||||||
}
|
}
|
||||||
|
|
@ -51,38 +49,38 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
return new CharacterReference(line.Content[idx], new SourceLocation(_filePath, absoluteIndex, line.Index, idx));
|
return new CharacterReference(line.Content[idx], new SourceLocation(_filePath, absoluteIndex, line.Index, idx));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Append(char[] content)
|
private void BuildTextLines(char[] content)
|
||||||
{
|
{
|
||||||
|
string lineText;
|
||||||
|
var lineStart = 0;
|
||||||
|
|
||||||
for (int i = 0; i < content.Length; i++)
|
for (int i = 0; i < content.Length; i++)
|
||||||
{
|
{
|
||||||
AppendCore(content[i]);
|
if (ParserHelpers.IsNewLine(content[i]))
|
||||||
|
|
||||||
// \r on it's own: Start a new line, otherwise wait for \n
|
|
||||||
// Other Newline: Start a new line
|
|
||||||
if ((content[i] == '\r' && (i + 1 == content.Length || content[i + 1] != '\n')) || (content[i] != '\r' && ParserHelpers.IsNewLine(content[i])))
|
|
||||||
{
|
{
|
||||||
PushNewLine();
|
// \r on it's own: Start a new line, otherwise wait for \n
|
||||||
|
// Other Newline: Start a new line
|
||||||
|
if (content[i] == '\r' && i + 1 < content.Length && content[i + 1] == '\n')
|
||||||
|
{
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
lineText = new string(content, lineStart, (i - lineStart) + 1); // +1 to include the current char
|
||||||
|
_lines.Add(new TextLine(lineStart, _lines.Count, lineText));
|
||||||
|
|
||||||
|
lineStart = i + 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
private void PushNewLine()
|
lineText = new string(content, lineStart, content.Length - lineStart); // no +1 as content.Length points past the last char already
|
||||||
{
|
_lines.Add(new TextLine(lineStart, _lines.Count, lineText));
|
||||||
_endLine = new TextLine(_endLine.End, _endLine.Index + 1);
|
|
||||||
_lines.Add(_endLine);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void AppendCore(char chr)
|
|
||||||
{
|
|
||||||
Debug.Assert(_lines.Count > 0);
|
|
||||||
_lines[_lines.Count - 1].Content.Append(chr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private TextLine FindLine(int absoluteIndex)
|
private TextLine FindLine(int absoluteIndex)
|
||||||
{
|
{
|
||||||
TextLine selected = null;
|
TextLine selected;
|
||||||
|
|
||||||
if (_currentLine == null)
|
if (_currentLine.IsDefault)
|
||||||
{
|
{
|
||||||
// Scan from line 0
|
// Scan from line 0
|
||||||
selected = ScanLines(absoluteIndex, 0, _lines.Count);
|
selected = ScanLines(absoluteIndex, 0, _lines.Count);
|
||||||
|
|
@ -104,6 +102,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
selected = ScanLines(absoluteIndex, _currentLine.Index, _lines.Count);
|
selected = ScanLines(absoluteIndex, _currentLine.Index, _lines.Count);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
selected = default;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if (absoluteIndex < _currentLine.Start)
|
else if (absoluteIndex < _currentLine.Start)
|
||||||
{
|
{
|
||||||
|
|
@ -122,6 +124,10 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
selected = ScanLines(absoluteIndex, 0, _currentLine.Index);
|
selected = ScanLines(absoluteIndex, 0, _currentLine.Index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
selected = default;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
@ -129,7 +135,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
selected = _currentLine;
|
selected = _currentLine;
|
||||||
}
|
}
|
||||||
|
|
||||||
Debug.Assert(selected == null || selected.Contains(absoluteIndex));
|
Debug.Assert(selected.IsDefault || selected.Contains(absoluteIndex));
|
||||||
_currentLine = selected;
|
_currentLine = selected;
|
||||||
return selected;
|
return selected;
|
||||||
}
|
}
|
||||||
|
|
@ -159,7 +165,7 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal struct CharacterReference
|
internal struct CharacterReference
|
||||||
|
|
@ -175,28 +181,26 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
public SourceLocation Location { get; }
|
public SourceLocation Location { get; }
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TextLine
|
private struct TextLine
|
||||||
{
|
{
|
||||||
private StringBuilder _content = new StringBuilder();
|
public TextLine(int start, int index, string content)
|
||||||
|
|
||||||
public TextLine(int start, int index)
|
|
||||||
{
|
{
|
||||||
Start = start;
|
Start = start;
|
||||||
Index = index;
|
Index = index;
|
||||||
|
Content = content;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StringBuilder Content
|
public string Content { get; }
|
||||||
{
|
|
||||||
get { return _content; }
|
public bool IsDefault => Content == null;
|
||||||
}
|
|
||||||
|
|
||||||
public int Length
|
public int Length
|
||||||
{
|
{
|
||||||
get { return Content.Length; }
|
get { return Content.Length; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Start { get; set; }
|
public int Start { get; }
|
||||||
public int Index { get; set; }
|
public int Index { get; }
|
||||||
|
|
||||||
public int End
|
public int End
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -16,25 +16,25 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
private readonly TokenizerView<TTokenizer> _tokenizer;
|
private readonly TokenizerView<TTokenizer> _tokenizer;
|
||||||
private SyntaxListBuilder<SyntaxToken>? _tokenBuilder;
|
private SyntaxListBuilder<SyntaxToken>? _tokenBuilder;
|
||||||
|
|
||||||
// Following four high traffic methods cached as using method groups would cause allocation on every invocation.
|
// Following four high traffic methods cached as using method groups would cause allocation on every invocation.
|
||||||
protected static readonly Func<SyntaxToken, bool> IsSpacingToken = (token) =>
|
protected static readonly Func<SyntaxToken, bool> IsSpacingToken = (token) =>
|
||||||
{
|
{
|
||||||
return token.Kind == SyntaxKind.Whitespace;
|
return token.Kind == SyntaxKind.Whitespace;
|
||||||
};
|
};
|
||||||
|
|
||||||
protected static readonly Func<SyntaxToken, bool> IsSpacingTokenIncludingNewLines = (token) =>
|
protected static readonly Func<SyntaxToken, bool> IsSpacingTokenIncludingNewLines = (token) =>
|
||||||
{
|
{
|
||||||
return IsSpacingToken(token) || token.Kind == SyntaxKind.NewLine;
|
return IsSpacingToken(token) || token.Kind == SyntaxKind.NewLine;
|
||||||
};
|
};
|
||||||
|
|
||||||
protected static readonly Func<SyntaxToken, bool> IsSpacingTokenIncludingComments = (token) =>
|
protected static readonly Func<SyntaxToken, bool> IsSpacingTokenIncludingComments = (token) =>
|
||||||
{
|
{
|
||||||
return IsSpacingToken(token) || token.Kind == SyntaxKind.CSharpComment;
|
return IsSpacingToken(token) || token.Kind == SyntaxKind.CSharpComment;
|
||||||
};
|
};
|
||||||
|
|
||||||
protected static readonly Func<SyntaxToken, bool> IsSpacingTokenIncludingNewLinesAndComments = (token) =>
|
protected static readonly Func<SyntaxToken, bool> IsSpacingTokenIncludingNewLinesAndComments = (token) =>
|
||||||
{
|
{
|
||||||
return IsSpacingTokenIncludingNewLines(token) || token.Kind == SyntaxKind.CSharpComment;
|
return IsSpacingTokenIncludingNewLines(token) || token.Kind == SyntaxKind.CSharpComment;
|
||||||
};
|
};
|
||||||
|
|
||||||
protected TokenizerBackedParser(LanguageCharacteristics<TTokenizer> language, ParserContext context)
|
protected TokenizerBackedParser(LanguageCharacteristics<TTokenizer> language, ParserContext context)
|
||||||
|
|
@ -224,7 +224,19 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
|
|
||||||
protected internal bool NextIs(SyntaxKind type)
|
protected internal bool NextIs(SyntaxKind type)
|
||||||
{
|
{
|
||||||
return NextIs(token => token != null && type == token.Kind);
|
// Duplicated logic with NextIs(Func...) to prevent allocation
|
||||||
|
var cur = CurrentToken;
|
||||||
|
var result = false;
|
||||||
|
if (NextToken())
|
||||||
|
{
|
||||||
|
result = (type == CurrentToken.Kind);
|
||||||
|
PutCurrentBack();
|
||||||
|
}
|
||||||
|
|
||||||
|
PutBack(cur);
|
||||||
|
EnsureCurrent();
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected internal bool NextIs(params SyntaxKind[] types)
|
protected internal bool NextIs(params SyntaxKind[] types)
|
||||||
|
|
@ -235,21 +247,17 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
protected internal bool NextIs(Func<SyntaxToken, bool> condition)
|
protected internal bool NextIs(Func<SyntaxToken, bool> condition)
|
||||||
{
|
{
|
||||||
var cur = CurrentToken;
|
var cur = CurrentToken;
|
||||||
|
var result = false;
|
||||||
if (NextToken())
|
if (NextToken())
|
||||||
{
|
{
|
||||||
var result = condition(CurrentToken);
|
result = condition(CurrentToken);
|
||||||
PutCurrentBack();
|
PutCurrentBack();
|
||||||
PutBack(cur);
|
|
||||||
EnsureCurrent();
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
PutBack(cur);
|
|
||||||
EnsureCurrent();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
PutBack(cur);
|
||||||
|
EnsureCurrent();
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected internal bool Was(SyntaxKind type)
|
protected internal bool Was(SyntaxKind type)
|
||||||
|
|
@ -289,11 +297,11 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
|
||||||
|
|
||||||
protected internal IReadOnlyList<SyntaxToken> ReadWhile(Func<SyntaxToken, bool> condition)
|
protected internal IReadOnlyList<SyntaxToken> ReadWhile(Func<SyntaxToken, bool> condition)
|
||||||
{
|
{
|
||||||
if (!EnsureCurrent() || !condition(CurrentToken))
|
if (!EnsureCurrent() || !condition(CurrentToken))
|
||||||
{
|
{
|
||||||
return Array.Empty<SyntaxToken>();
|
return Array.Empty<SyntaxToken>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var result = new List<SyntaxToken>();
|
var result = new List<SyntaxToken>();
|
||||||
do
|
do
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue