Reduce Enum.GetEnumName overheads (#76162)
authorStephen Toub <stoub@microsoft.com>
Thu, 29 Sep 2022 11:37:41 +0000 (07:37 -0400)
committerGitHub <noreply@github.com>
Thu, 29 Sep 2022 11:37:41 +0000 (07:37 -0400)
* Reduce Enum.GetEnumName overheads

This also helps Enum.ToString(), Enum.IsDefined, etc.  Many enums are composed of sequential values starting at 0 (half of all enums in corelib, for example, meet this criteria).  Today when looking up a value, we search the array of values, but if an enum is known to have such sequential values, we can instead just index into the array at the appropriate location.

* Address PR feedback

src/coreclr/System.Private.CoreLib/src/System/Enum.CoreCLR.cs
src/coreclr/nativeaot/System.Private.CoreLib/src/System/Reflection/EnumInfo.cs
src/libraries/System.Private.CoreLib/src/System/Enum.EnumInfo.cs
src/libraries/System.Private.CoreLib/src/System/Enum.cs

index fb1dd0a..18d9a6d 100644 (file)
@@ -72,11 +72,15 @@ namespace System
             return underlyingType;
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         private static EnumInfo GetEnumInfo(RuntimeType enumType, bool getNames = true)
         {
-            EnumInfo? entry = enumType.GenericCache as EnumInfo;
+            return enumType.GenericCache is EnumInfo info && (!getNames || info.Names is not null) ?
+                info :
+                InitializeEnumInfo(enumType, getNames);
 
-            if (entry == null || (getNames && entry.Names == null))
+            [MethodImpl(MethodImplOptions.NoInlining)]
+            static EnumInfo InitializeEnumInfo(RuntimeType enumType, bool getNames)
             {
                 ulong[]? values = null;
                 string[]? names = null;
@@ -88,11 +92,10 @@ namespace System
                     getNames ? Interop.BOOL.TRUE : Interop.BOOL.FALSE);
                 bool hasFlagsAttribute = enumType.IsDefined(typeof(FlagsAttribute), inherit: false);
 
-                entry = new EnumInfo(hasFlagsAttribute, values!, names!);
+                var entry = new EnumInfo(hasFlagsAttribute, values!, names!);
                 enumType.GenericCache = entry;
+                return entry;
             }
-
-            return entry;
         }
     }
 }
index 761a388..8750967 100644 (file)
@@ -65,6 +65,16 @@ namespace System.Reflection
             Array.Copy(rawValues, ValuesAsUnderlyingType, numValues);
 
             HasFlagsAttribute = isFlags;
+
+            ValuesAreSequentialFromZero = true;
+            for (int i = 0; i < values.Length; i++)
+            {
+                if (values[i] != (ulong)i)
+                {
+                    ValuesAreSequentialFromZero = false;
+                    break;
+                }
+            }
         }
 
         internal Type UnderlyingType { get; }
@@ -72,5 +82,6 @@ namespace System.Reflection
         internal ulong[] Values { get; }
         internal Array ValuesAsUnderlyingType { get; }
         internal bool HasFlagsAttribute { get; }
+        internal bool ValuesAreSequentialFromZero { get; }
     }
 }
index 7ab48cf..67f4d58 100644 (file)
@@ -8,6 +8,7 @@ namespace System
         internal sealed class EnumInfo
         {
             public readonly bool HasFlagsAttribute;
+            public readonly bool ValuesAreSequentialFromZero;
             public readonly ulong[] Values;
             public readonly string[] Names;
 
@@ -17,6 +18,17 @@ namespace System
                 HasFlagsAttribute = hasFlagsAttribute;
                 Values = values;
                 Names = names;
+
+                // Store whether all of the values are sequential starting from zero.
+                ValuesAreSequentialFromZero = true;
+                for (int i = 0; i < values.Length; i++)
+                {
+                    if (values[i] != (ulong)i)
+                    {
+                        ValuesAreSequentialFromZero = false;
+                        break;
+                    }
+                }
             }
         }
     }
index 7e1ac66..d35f3a4 100644 (file)
@@ -116,12 +116,24 @@ namespace System
             return GetEnumName(GetEnumInfo(enumType), ulValue);
         }
 
+        [MethodImpl(MethodImplOptions.AggressiveInlining)]
         private static string? GetEnumName(EnumInfo enumInfo, ulong ulValue)
         {
-            int index = FindDefinedIndex(enumInfo.Values, ulValue);
-            if (index >= 0)
+            if (enumInfo.ValuesAreSequentialFromZero)
+            {
+                string[] names = enumInfo.Names;
+                if (ulValue < (ulong)names.Length)
+                {
+                    return names[(uint)ulValue];
+                }
+            }
+            else
             {
-                return enumInfo.Names[index];
+                int index = FindDefinedIndex(enumInfo.Values, ulValue);
+                if (index >= 0)
+                {
+                    return enumInfo.Names[index];
+                }
             }
 
             return null; // return null so the caller knows to .ToString() the input
@@ -302,7 +314,7 @@ namespace System
 
         internal static string[] InternalGetNames(RuntimeType enumType) =>
             // Get all of the names
-            GetEnumInfo(enumType, true).Names;
+            GetEnumInfo(enumType).Names;
 
         public static Type GetUnderlyingType(Type enumType)
         {
@@ -411,16 +423,21 @@ namespace System
         internal static ulong[] InternalGetValues(RuntimeType enumType)
         {
             // Get all of the values
-            return GetEnumInfo(enumType, false).Values;
+            return GetEnumInfo(enumType, getNames: false).Values;
         }
 
         public static bool IsDefined<TEnum>(TEnum value) where TEnum : struct, Enum
         {
             RuntimeType enumType = (RuntimeType)typeof(TEnum);
-            ulong[] ulValues = Enum.InternalGetValues(enumType);
-            ulong ulValue = Enum.ToUInt64(value);
-
-            return FindDefinedIndex(ulValues, ulValue) >= 0;
+            EnumInfo info = GetEnumInfo(enumType, getNames: false);
+            ulong ulValue = ToUInt64(value);
+            ulong[] ulValues = info.Values;
+
+            // If the enum's values are all sequentially numbered starting from 0, then we can
+            // just return if the requested index is in range. Otherwise, search for the value.
+            return
+                info.ValuesAreSequentialFromZero ? ulValue < (ulong)ulValues.Length :
+                FindDefinedIndex(ulValues, ulValue) >= 0;
         }
 
         [MethodImpl(MethodImplOptions.AggressiveInlining)]