‘in’ will make your code slower

Problem

The new in keyword (for parameters) in C# 7.2 promises to make code faster:

When you add the in modifier to pass an argument by reference, you declare your design intent is to pass arguments by reference to avoid unnecessary copying.

However, naïve use of this modifier will result in more copies (and slower code)!

This side-effect is implied by the MSDN documentation:

You can call any instance method that uses pass-by-value semantics. In those instances, a copy of the in parameter is created.

It’s also mentioned in passing in the readonly ref proposal:

After adding support for in parameters and ref redonly [sic] returns the problem of defensive copying will get worse since readonly variables will become more common.

Consider the example method from MSDN:

private static double CalculateDistance(in Point3D point1, in Point3D point2)
{
    double xDifference = point1.X - point2.X;
    double yDifference = point1.Y - point2.Y;
    double zDifference = point1.Z - point2.Z;

    return Math.Sqrt(xDifference * xDifference + yDifference * yDifference + zDifference * zDifference);
}

And assume this implementation of Point3D:

public struct Point3D
{
    public Point3D(double x, double y, double z)
    {
        X = x;
        Y = y;
        Z = z;
    }

    public double X { get; }
    public double Y { get; }
    public double Z { get; }
}

A number of C# features now combine in an unfortunate way:

  1. An in parameter is readonly
  2. Calling an instance method on a readonly struct makes a copy
    • Because the method might mutate this, a copy has to be made to ensure the readonly value isn’t modified
  3. Property accessors are instance methods

Every time a property on an in parameter is accessed in CalculateDistance, the compiler has to defensively create a temporary copy of the parameter. We’ve now gone from avoiding one copy per argument (at the call site) to three copies per argument (inside the method body)!

This is not a new problem; see Jon Skeet’s post on The Surprising Inefficiency of Readonly Fields. But using in makes it a much more common problem.

Solution

The solution is also in C# 7.2: readonly struct.

If we change public struct Point3D to public readonly struct Point3D (the implementation doesn’t have to change because all fields are already readonly), then the compiler knows it can elide the temporary copy inside the body of CalculateDistance. This makes the method faster than passing the structs by value.

Note that we could have achieved the same effect in C# 7.1 by passing the struct by ref. However, this allows the caller to mutate its fields (if it’s mutable) or reassign the entire variable to a new value. Using in expresses the intent that the caller will not modify the variable at all (and the compiler enforces that).

Demonstration

I’ve created a test harness that benchmarks the various combinations of in, ref, struct and readonly struct. (Note that I increased the struct size to 56 bytes to make the differences more obvious; smaller structs may not be impacted as much.) The full benchmark results are in that repo; the summary is:

Method Mean
PointByValue 25.09 ns
PointByRef 21.77 ns
PointByIn 34.59 ns
ReadOnlyPointByValue 25.29 ns
ReadOnlyPointByRef 21.78 ns
ReadOnlyPointByIn 21.79 ns

Summary

  • If you’re using in to express design intent (instead of ref), be aware that there may be a slight performance penalty when passing large structs.
  • If you’re using in to avoid copies and improve performance, only use it with readonly struct.

Posted by Bradley Grainger on December 07, 2017