aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Skeet <skeet@pobox.com>2015-01-31 08:56:52 +0000
committerJon Skeet <skeet@pobox.com>2015-01-31 08:56:52 +0000
commit9dc7d7225ddbc36beff3d0117990964e8ee1a4c8 (patch)
tree56971b1bcc0407ebd28dc8d031830ded876e44b2
parent391827537689e3674cab9b1157dc4aed53f64464 (diff)
downloadprotobuf-9dc7d7225ddbc36beff3d0117990964e8ee1a4c8.tar.gz
protobuf-9dc7d7225ddbc36beff3d0117990964e8ee1a4c8.tar.bz2
protobuf-9dc7d7225ddbc36beff3d0117990964e8ee1a4c8.zip
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.
-rw-r--r--src/ProtocolBuffers/CodedInputStream.cs84
1 files 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
/// <summary>
/// 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.
/// </summary>
[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<T>.TryConvert(number, ref value))
{
unknown = null;
- value = (T) (object) number;
return true;
}
unknown = number;
@@ -1861,5 +1860,84 @@ namespace Google.ProtocolBuffers
}
#endregion
+
+ /// <summary>
+ /// Helper class to make parsing enums faster.
+ /// </summary>
+ private static class EnumHelper<T> where T : struct
+ {
+ /// <summary>
+ /// We use the array form if all values are in the range [0, LimitForArray),
+ /// otherwise we build a dictionary.
+ /// </summary>
+ private const int LimitForArray = 32;
+ // Only one of these will be populated.
+ private static readonly Dictionary<int, T> 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<int, T>();
+ foreach (int number in array)
+ {
+ dictionary[number] = (T)(object)number;
+ }
+ }
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ 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