Enums and Performance

9/26/2004 4:19:14 PM

Enums and Performance

While enums are value types and are often recognized and treated like standard integral values by the runtime (in IL, enums and integers have almost no distinction), there are few performance caveats to using them.

Enumerated types are derived from ValueType and Enum (as well as Object), which are, ironically, reference types. An explicit conversion of an enum value to ValueType, will actually perform boxing and generate an object reference.

Any calls to an inherited method from any of those classes will also actually invoke boxing, prior to calling the base method. This includes the following methods: GetType(), ToString(), GetHashCode() and Equals(). In addition the costs of mplicit boxing is the far larger costs of reflection used to actually complete the said methods.

ToString uses reflection, the first time it is called, to dynamically retrieve enumeration constants from the enumerated type and stores those values into a hash table for future use. However, GetHashCode always uses reflection to retrieve the underlying value. While ValueType.Equals will attempt to do a fast bit check, when a valuetype with no reference methods, such as is the case for enumerated types, it won't be faster than a direct compare.

This is true for any value type, but normally the cost can be eliminated for ToString, GetHashCode, and Equals, by simply overriding those methods and avoiding calls to the base methods. However, those methods CANNOT be overridden for enumerated types.

I didn't mention interface methods like IComparable, but the same performance implications apply to inherited interface methods as well. Actually, IComparable is especially egregrious. The call to CompareTo for enumerations actually generates multiple boxing calls per call, yet this is a method, where performance is often critical, as it is typically called for each member of a collection. Enumerated types do not support the generics interface, IComparable<T>.

One ironic conclusion, we can reach is that a generics collection of enumerated values is very likely to perform (in time, not space) worse than a non-generics version. This is because calling any method from an enumerated type will cause boxing (for a unboxed value), but the boxing cost can be eliminated altogether, if the collection stores enumerated values as reference values in the first place.

ArrayList or List<object> should sort objects of enumerated type T faster than List<T>, since at least n log(n) boxing calls are eliminated from the operation. Dictionary<object> likely executes faster than Dictionary<T>, because the enumerated type will not be boxed (they will have already been pre-boxed) at each call to GetHashCode and Equals. Boxing itself is an expensive operation, ignoring the fact that memory is allocated.

Another ironic conclusion is that creating your own version of an enumerated type, not derived from Enum, is going to be faster inside collections (enums will still probably be faster inside functions) than the CLR versions, because you can ensure that GetHashCode, Equals, ToString, IComparable, and IComparable<T> are not inherited from any of base classes such as ValueType.

Some clarifications based on comment feedback:

If a value type overrides ToString(), etcs, the resulting override is actually non-virtual, because structs are sealed (not allowed to be inherited). There is no need for the runtime to box the value type before calling To.String(). You can see this, running Reflector. Let's say, we have a value type T. If we don't override ToString() in class T, then, in IL, when we must call the method named Object.ToString, which requires a boxing call. If we override ToString in class T, we can call T.ToString directly without boxing. 







Net Undocumented is a blog about the internals of .NET including Xamarin implementations. Other topics include managed and web languages (C#, C++, Javascript), computer science theory, software engineering and software entrepreneurship.

Social Media