Exploring the .NET CoreFX Part 8: NullReferenceException Performance Tricks

Exploring the .NET CoreFX Part 8: NullReferenceException Performance Tricks

Page content

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

The .NET Core’s System.Collections.Immutable.ImmutableArray class implements an immutable wrapper around a normal C# managed array. This looks something like:

public struct ImmutableArray<T>
{
    internal T[] array;
    ...
}

ImmutableArray.array is lazy-initialized.

Within the ImmutableArray class, there are a number of methods which have the precondition that ImmutableArray.array must be initialized. These preconditions must be checked before the method begins processing to make sure we handle invalid states correctly.

One example is ImmutableArray.IndexOf, which could have been implemented as:

public struct ImmutableArray<T>
{
    internal T[] array;

    public int IndexOf(T item)
    {
        if (this.array == null)
            throw new NullReferenceException(); // Or maybe InvalidOperationException?
        ...
    }
}

However, the .NET Core team was more clever than that. They realized that they could avoid a (likely easily-predicted) branch and instead implemented it as follows:

public struct ImmutableArray<T>
{
    internal T[] array;

    public int IndexOf(T item)
    {
        this.ThrowNullRefIfNotInitialized();
        ...
    }

    /// <summary>
    /// Throws a null reference exception if the array field is null.
    /// </summary>
    internal void ThrowNullRefIfNotInitialized()
    {
        // Force NullReferenceException if array is null by touching its Length.
        // This way of checking has a nice property of requiring very little code
        // and not having any conditions/branches.
        // In a faulting scenario we are relying on hardware to generate the fault.
        // And in the non-faulting scenario (most common) the check is virtually free since
        // if we are going to do anything with the array, we will need Length anyways
        // so touching it, and potentially causing a cache miss, is not going to be an
        // extra expense.
        var unused = this.array.Length;

        // This line is a workaround for a bug in C# compiler
        // The code in this line will not be emitted, but it will prevent incorrect
        // optimizing away of "Length" call above in Release builds.
        // TODO: remove the workaround when building with Roslyn which does not have this bug.
        var unused2 = unused;
    }
}

Personally I’d be deathly afraid that a more-clever compiler would remove this code entirely.  I would not implement this pattern unless I had excellent automated unit test coverage for this scenario.

Recommendations

  1. While it may be possible to avoid if (x == null) checks with clever member dereferences, don’t do it; it’s too risky.