Because the method might mutate this, a copy has to be made to ensure the readonly value isn’t modified

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)!

Solution

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.