2

In C# we can specify in on struct method parameters so that they are passed as readonly references:

public int TimesTwo(in int number)
{
    return number * 2;
}

This is almost the same as using ref but does not allow modifications to the parameter:

public int TimesTwo(in int number)
{
    number *= 2; // Compiler error
    return number;
}

Additionally it does not require specifying the keyword when calling, like ref does:

var x = 1;
var y = TimesTwo(x);
var z = TimesTwo(in x);
// y and z are both 2

In some scenarios this is not possible, like when you need to modify the parameter or with async or iterator methods which do not allow in. But the question is, in 99% of cases where the parameter is only read then why not specify in?

When in is not specified, the parameter passed is copied to a local variable. This copying can take time that can be noticeable for large structs in tight loops, as Microsoft says here.

With in, the reference is passed, so no copying takes place. Maybe the time saved not copying is negligible in most cases, but I want to know if there is any reason other than the standard "it clutters the code" or "it's a micro-optimization you shouldn't be worrying about".

As far as I can tell this "micro-optimization" of bypassing struct copying is exactly why in was introduced. Are there other reasons why this would be bad practice to just throw it in everywhere for performance critical code?

pb7280
  • 33
  • 1
  • 3
  • _"In C# we can specify in on struct method parameters so that they are passed as readonly references"_ Note that this is only possible in C# 7.2 – ProgrammingLlama Feb 01 '19 at 04:27
  • For these kinds of questions, I find https://ericlippert.com/2012/12/17/performance-rant/ helpful. Also, some of it is thinking about how things are worded. Microsoft's choice of wording `Some struct type arguments may be large in size, and when methods are called in tight loops or critical code paths, the cost of copying those structures is critical.` Note they _explicitly_ mentioned `large` - so I'd be asking myself 'why did they use that?' Is there something different between large and small structs? – mjwills Feb 01 '19 at 22:56
  • Possible duplicate of [Does passing values by reference improve speed significantly?](https://stackoverflow.com/questions/3501939/does-passing-values-by-reference-improve-speed-significantly) – mjwills Feb 01 '19 at 22:56

1 Answers1

5

It is also good for tight coupling. For example the following method from MSDN (https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/in-parameter-modifier):

static void Method(in int argument)
{
    // implementation removed
}

Method(5); // ok
Method(5L); // CS1503: no implicit conversion from long to int

Here's an interesting link for optimization (I copied their results) which discusses about readonly struct, readonly ref and in: https://faithlife.codes/blog/2017/12/in-will-make-your-code-slower/

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

Some further reading (Credit: Jon Skeet):

MICRO-OPTIMIZATION: THE SURPRISING INEFFICIENCY OF READONLY FIELDS

https://codeblog.jonskeet.uk/2014/07/16/micro-optimization-the-surprising-inefficiency-of-readonly-fields/

Gauravsa
  • 6,330
  • 2
  • 21
  • 30
  • That was an interesting read, and definitely a problem I wasn't considering! Particularly "Calling an instance method on a `readonly` struct makes a copy". It seems from their conclusion and the test results, there is decent optimization in using `in` vs `value` **if the struct is marked `readonly`**. But with a struct not marked `readonly`, even if it is semantically readonly, there may be an even more noticeable performance penalty when calling instance methods. – pb7280 Feb 01 '19 at 22:38
  • It seems the benchmark linked in this answer was done with a C# language version where the necessary optimization for structs passed by `in` is turned off. Take a look at my [discovery](https://github.com/bgrainger/InPerformance/issues/2). – ceztko May 12 '23 at 15:39
  • By empirical tests, the `in` parameter optimization is turned on by setting the project `LangVersion` at least to [8.0](https://github.com/bgrainger/InPerformance/issues/2#issuecomment-1545961441). – ceztko May 12 '23 at 16:00