2

I've read an article on MSDN. It explains why "in" should ONLY be used with custom readonly structs, otherwise there will be performance penalties. However, I didn't quite understand how to use "in" with primitive types. Since all built in value types in C# are immutable, does it mean that passing them by reference using "in" modifier will slightly improve performance compare to passing them by value?

Example:

public class Product
{
    private readonly int _weight;
    private readonly decimal _price;

    public Product(in decimal price, in int weight)
    {
        _price = price;
        _weight = weight;
    }
}

vs

public class Product
{
    private readonly int _weight;
    private readonly decimal _price;

    public Product(decimal price, int weight)
    {
        _price = price;
        _weight = weight;
    }
}
Nik
  • 77
  • 6

1 Answers1

5

The in modifier helps performance by avoiding unnecessary copies of value-types when invoking a method. Note that value-types (i.e. structs) are not actually automatically immutable (but the C# compiler does gives the appearance of immutability by making "defensive copies" and then overwriting the entire parent struct when setting a property value, this is explained in the article you linked to).

Given that structs are copied in their entirety in method calls if you didn't use in (or out/ref), you could improve performance by only passing a pointer to a struct object higher-up in the call-stack because a pointer (a reference in .NET) is smaller than a struct, but this is only avoiding a copy if the struct is also truly immutable.

C#'s built-in value-types (Int16, Int32, Double, UInt64, etc) are all smaller than (or the same size as) a pointer on 64-bit systems (except Decimal which is a 128-bit type, and String which is a reference-type), which means there is zero benefit to using the in modifier with those types. You will also suffer a performance hit from incurring the cost of a pointer dereference - which may also cause a processor memory cache miss.

Consider some different scenarios below (all examples assume x64 and no optimizations that change the semantics of the method calls or the calling convention):

Passing a small value-type

public static void Main()
{
    Int32 value = 123; // 4 bytes
    Foo( in value ); // Get an 8-byte pointer to `value`, then pass that
}

public static void Foo( in Int32 x ) { ... }

There is a performance hit because now the computer is passing an 8-byte pointer value that also needs dereferencing instead of a 4-byte value that can be used immediately.

Passing a large value-type

public struct MyBigStruct
{
    public Decimal Foo;
    public Decimal Bar;
    public Decimal Baz;
}

public static void Main()
{
    MyBigStruct value; // 48 bytes
    Foo( in value ); // Get an 8-byte pointer to `value`, then pass that
}

public static void Foo( in MyBigStruct x ) { ... }

There is a likely performance gain because the computer is passing an 8-byte pointer value instead of copying a 48-byte value, but the pointer dereference may be more expensive than copying the extra 32 bytes. You should profile at runtime to decide if the change is worthwhile. This also makes x in Foo immutable because otherwise value in Main would be modified.

Servy
  • 202,030
  • 26
  • 332
  • 449
Dai
  • 141,631
  • 28
  • 261
  • 374
  • structs, just like *literally every single type in C#*, is passed by value whenever not using `ref/out/in`, and structs, just like *every single type in C#*, is passed by reference when using `ref/out/in`. The type of the parameter has no effect on whether it's passed by value or reference. – Servy Oct 26 '18 at 16:18
  • @Servy That depends on the semantics of "passed by reference". Yes, the *reference* is passed-by-value, but using that precise terminology can be confusing. Jon Skeet wrote this about it back in 2004: https://blogs.msdn.microsoft.com/csharpfaq/2004/03/11/how-are-parameters-passed-in-c-are-they-passed-by-reference-or-by-value/ "when the type of the parameter is a reference type, you’re passing a reference rather than an actual object" – Dai Oct 26 '18 at 16:25
  • Yes, Jon uses accurate terminology in saying that a reference type results in the parameter that is being passed, whether passed by reference or value, is itself a reference. Your answer does not. Saying that reference types are passed by reference and that value types are passed by value is incorrect. Proper terminology on the subject is important, particularly in questions asking about the semantics of passing a variable by reference, as this one is. – Servy Oct 26 '18 at 17:17
  • @Servy Indeed - but I don't alude to "pass-by-reference" or "pass-by-value" in my answer. In an earlier revision I did use "pass-by-value" in the context of structs, but I don't believe I was being inaccurate or over-simplified. – Dai Oct 26 '18 at 18:17
  • But you were being inaccurate. You said that structs are passed by value. That's wrong. *All* parameters, regardless of type, are passed by value if there is no `out/ref/in`, and they are all passed by reference if you do have one of those three. – Servy Oct 26 '18 at 18:51
  • @Servy Please edit my answer if you feel it can be made more accurate. – Dai Oct 26 '18 at 19:00