From 9dc7d7225ddbc36beff3d0117990964e8ee1a4c8 Mon Sep 17 00:00:00 2001 From: Jon Skeet Date: Sat, 31 Jan 2015 08:56:52 +0000 Subject: Optimize enum parsing. In a small enum-heavy benchmark, this improved the time of the benchmark (parsing a repeated message, where each message had 5 "small" enum values and 5 "large" enum values) from ~39s to ~11s. There is a small memory cost per enum used, but I expect this to be trivial compared with other per-type costs. Fixes issue 97. --- src/ProtocolBuffers/CodedInputStream.cs | 84 +++++++++++++++++++++++++++++++-- 1 file changed, 81 insertions(+), 3 deletions(-) diff --git a/src/ProtocolBuffers/CodedInputStream.cs b/src/ProtocolBuffers/CodedInputStream.cs index 293b735a..5e7adf57 100644 --- a/src/ProtocolBuffers/CodedInputStream.cs +++ b/src/ProtocolBuffers/CodedInputStream.cs @@ -474,7 +474,7 @@ namespace Google.ProtocolBuffers /// /// Reads an enum field value from the stream. If the enum is valid for type T, - /// then the ref value is set and it returns true. Otherwise the unkown output + /// then the ref value is set and it returns true. Otherwise the unknown output /// value is set and this method returns false. /// [CLSCompliant(false)] @@ -482,10 +482,9 @@ namespace Google.ProtocolBuffers where T : struct, IComparable, IFormattable { int number = (int) ReadRawVarint32(); - if (Enum.IsDefined(typeof(T), number)) + if (EnumHelper.TryConvert(number, ref value)) { unknown = null; - value = (T) (object) number; return true; } unknown = number; @@ -1861,5 +1860,84 @@ namespace Google.ProtocolBuffers } #endregion + + /// + /// Helper class to make parsing enums faster. + /// + private static class EnumHelper where T : struct + { + /// + /// We use the array form if all values are in the range [0, LimitForArray), + /// otherwise we build a dictionary. + /// + private const int LimitForArray = 32; + // Only one of these will be populated. + private static readonly Dictionary dictionary; + private static readonly T?[] values; + + static EnumHelper() + { + // It will actually be a T[], but the CLR will let us convert. + int[] array = (int[]) Enum.GetValues(typeof (T)); + if (array.Length == 0) + { + // Empty enum; model with an empty values array. + values = new T?[0]; + return; + } + int min = int.MaxValue; + int max = int.MinValue; + foreach (int number in array) + { + min = Math.Min(number, min); + max = Math.Max(number, max); + } + if (min >= 0 && max < LimitForArray) + { + values = new T?[max + 1]; + foreach (int number in array) + { + values[number] = (T)(object)number; + } + } + else + { + dictionary = new Dictionary(); + foreach (int number in array) + { + dictionary[number] = (T)(object)number; + } + } + } + + /// + /// Tries to convert an integer to its enum representation. This would take an out parameter, + /// but the caller uses ref, so this approach is simpler. + /// + internal static bool TryConvert(int number, ref T value) + { + if (values != null) + { + if (number < 0 || number >= values.Length) + { + return false; + } + T? maybeValue = values[number]; + if (maybeValue != null) + { + value = maybeValue.Value; + return true; + } + return false; + } + T converted; + if (dictionary.TryGetValue(number, out converted)) + { + value = converted; + return true; + } + return false; + } + } } } \ No newline at end of file -- cgit v1.2.3