Exploring the .NET CoreFX Part 10: Performance Tuning Enumeration

Exploring the .NET CoreFX Part 10: Performance Tuning Enumeration

Page content

This is part 10 of my Exploring the .NET CoreFX Series.

The .NET Core’s System.Collections.Immutable.ImmutableArray provides two enumerators. The first has been highly tuned for speed, and the second is a fallback for compatibility when it is required.

The high-performance enumerator uses the following performance optimizations:

  1. The enumerator is a struct, rather than a class, so that it is stack-allocated rather than heap-allocated.
  2. The enumerator does not implement IEnumerator or IEnumerator, as this would require it to implement IDisposable. By not implementing IDisposable the iterator will inline during foreach loops..
  3. The enumerator does not use range checks in Enumerator.Current; it requires on .NET’s array range checks to throw an exception instead.

The high-performance enumerator is called ImmutableArray.Enumerator, which is returned by ImmutableArray.GetEnumerator():

[Pure]
public Enumerator GetEnumerator()
{
    this.ThrowNullRefIfNotInitialized();
    return new Enumerator(this.array);
}

Note that this method is not defined by any interface ImmutableArray implements, but it will be used by foreach. This is because foreach is pattern-based rather than interface-based. What this means is that foreach does not require an object to implement IEnumerable as long as it implements a method named GetEnumerator(). Similarly, the returned object is not required to implement IEnumerator as long as it implements a Current property and a MoveNext() method.

The result of all this work is that foreach over an ImmutableArray is just as efficient as a hand-written for loop.

Recommendations

  1. To improve the performance of foreach, consider writing a specialized, struct-based enumerator in addition to the traditional one.