Improve CSharpLanguageCharacteristics.MapKeyword performance (dotnet/aspnetcore-tooling#1879)

* Improve CSharpLanguageCharacteristics.MapKeyword performance

The razor typing perf test profile I'm looking at has 156 ms of CPU cycles spent in this method, mostly in Enum.ToString()
\n\nCommit migrated from e821a4642e
This commit is contained in:
Todd Grunke 2020-05-11 12:42:39 -07:00 committed by GitHub
parent a70de6b67b
commit 016fd1ee9e
1 changed files with 107 additions and 3 deletions

View File

@ -1,6 +1,7 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Microsoft.AspNetCore.Razor.Language.Syntax.InternalSyntax;
@ -9,8 +10,6 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{
internal class CSharpLanguageCharacteristics : LanguageCharacteristics<CSharpTokenizer>
{
private static readonly CSharpLanguageCharacteristics _instance = new CSharpLanguageCharacteristics();
private static Dictionary<SyntaxKind, string> _tokenSamples = new Dictionary<SyntaxKind, string>()
{
{ SyntaxKind.Arrow, "->" },
@ -64,8 +63,108 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
{ SyntaxKind.Transition, "@" },
};
// Allows performance optimization of GetKeyword such that it need not do Enum.ToString
private static IReadOnlyDictionary<CSharpKeyword, string> _keywordNames = new Dictionary<CSharpKeyword, string>()
{
{ CSharpKeyword.Await, "await" },
{ CSharpKeyword.Abstract, "abstract" },
{ CSharpKeyword.Byte, "byte" },
{ CSharpKeyword.Class, "class" },
{ CSharpKeyword.Delegate, "delegate" },
{ CSharpKeyword.Event, "event" },
{ CSharpKeyword.Fixed, "fixed" },
{ CSharpKeyword.If, "if" },
{ CSharpKeyword.Internal, "internal" },
{ CSharpKeyword.New, "new" },
{ CSharpKeyword.Override, "override" },
{ CSharpKeyword.Readonly, "readonly" },
{ CSharpKeyword.Short, "short" },
{ CSharpKeyword.Struct, "struct" },
{ CSharpKeyword.Try, "try" },
{ CSharpKeyword.Unsafe, "unsafe" },
{ CSharpKeyword.Volatile, "volatile" },
{ CSharpKeyword.As, "as" },
{ CSharpKeyword.Do, "do" },
{ CSharpKeyword.Is, "is" },
{ CSharpKeyword.Params, "params" },
{ CSharpKeyword.Ref, "ref" },
{ CSharpKeyword.Switch, "switch" },
{ CSharpKeyword.Ushort, "ushort" },
{ CSharpKeyword.While, "while" },
{ CSharpKeyword.Case, "case" },
{ CSharpKeyword.Const, "const" },
{ CSharpKeyword.Explicit, "explicit" },
{ CSharpKeyword.Float, "float" },
{ CSharpKeyword.Null, "null" },
{ CSharpKeyword.Sizeof, "sizeof" },
{ CSharpKeyword.Typeof, "typeof" },
{ CSharpKeyword.Implicit, "implicit" },
{ CSharpKeyword.Private, "private" },
{ CSharpKeyword.This, "this" },
{ CSharpKeyword.Using, "using" },
{ CSharpKeyword.Extern, "extern" },
{ CSharpKeyword.Return, "return" },
{ CSharpKeyword.Stackalloc, "stackalloc" },
{ CSharpKeyword.Uint, "uint" },
{ CSharpKeyword.Base, "base" },
{ CSharpKeyword.Catch, "catch" },
{ CSharpKeyword.Continue, "continue" },
{ CSharpKeyword.Double, "double" },
{ CSharpKeyword.For, "for" },
{ CSharpKeyword.In, "in" },
{ CSharpKeyword.Lock, "lock" },
{ CSharpKeyword.Object, "object" },
{ CSharpKeyword.Protected, "protected" },
{ CSharpKeyword.Static, "static" },
{ CSharpKeyword.False, "false" },
{ CSharpKeyword.Public, "public" },
{ CSharpKeyword.Sbyte, "sbyte" },
{ CSharpKeyword.Throw, "throw" },
{ CSharpKeyword.Virtual, "virtual" },
{ CSharpKeyword.Decimal, "decimal" },
{ CSharpKeyword.Else, "else" },
{ CSharpKeyword.Operator, "operator" },
{ CSharpKeyword.String, "string" },
{ CSharpKeyword.Ulong, "ulong" },
{ CSharpKeyword.Bool, "bool" },
{ CSharpKeyword.Char, "char" },
{ CSharpKeyword.Default, "default" },
{ CSharpKeyword.Foreach, "foreach" },
{ CSharpKeyword.Long, "long" },
{ CSharpKeyword.Void, "void" },
{ CSharpKeyword.Enum, "enum" },
{ CSharpKeyword.Finally, "finally" },
{ CSharpKeyword.Int, "int" },
{ CSharpKeyword.Out, "out" },
{ CSharpKeyword.Sealed, "sealed" },
{ CSharpKeyword.True, "true" },
{ CSharpKeyword.Goto, "goto" },
{ CSharpKeyword.Unchecked, "unchecked" },
{ CSharpKeyword.Interface, "interface" },
{ CSharpKeyword.Break, "break" },
{ CSharpKeyword.Checked, "checked" },
{ CSharpKeyword.Namespace, "namespace" },
{ CSharpKeyword.When, "when" },
};
private static readonly CSharpLanguageCharacteristics _instance = new CSharpLanguageCharacteristics();
protected CSharpLanguageCharacteristics()
{
#if DEBUG
var values = Enum.GetValues(typeof(CSharpKeyword));
Debug.Assert(values.Length == _keywordNames.Count, "_keywordNames and CSharpKeyword are out of sync");
for (var i = 0; i < values.Length; i++)
{
var keyword = (CSharpKeyword) values.GetValue(i);
var expectedValue = keyword.ToString().ToLowerInvariant();
var actualValue = _keywordNames[keyword];
Debug.Assert(expectedValue == actualValue, "_keywordNames and CSharpKeyword are out of sync for " + expectedValue);
}
#endif
}
public static CSharpLanguageCharacteristics Instance => _instance;
@ -170,7 +269,12 @@ namespace Microsoft.AspNetCore.Razor.Language.Legacy
public static string GetKeyword(CSharpKeyword keyword)
{
return keyword.ToString().ToLowerInvariant();
if (!_keywordNames.TryGetValue(keyword, out var value))
{
value = keyword.ToString().ToLowerInvariant();
}
return value;
}
}
}