From 016fd1ee9e3e263072dae8f07601adfe0d911f3a Mon Sep 17 00:00:00 2001 From: Todd Grunke Date: Mon, 11 May 2020 12:42:39 -0700 Subject: [PATCH] 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 https://github.com/dotnet/aspnetcore-tooling/commit/e821a4642e2148329cdf35c1b6cbd64e7ab9ae10 --- .../Legacy/CSharpLanguageCharacteristics.cs | 110 +++++++++++++++++- 1 file changed, 107 insertions(+), 3 deletions(-) diff --git a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/CSharpLanguageCharacteristics.cs b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/CSharpLanguageCharacteristics.cs index c251448df4..febc9f6493 100644 --- a/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/CSharpLanguageCharacteristics.cs +++ b/src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/CSharpLanguageCharacteristics.cs @@ -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 { - private static readonly CSharpLanguageCharacteristics _instance = new CSharpLanguageCharacteristics(); - private static Dictionary _tokenSamples = new Dictionary() { { 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 _keywordNames = new Dictionary() + { + { 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; } } } \ No newline at end of file