我试图测试添加到C#中的“in”关键字的性能(或不)。 in关键字应该能够将对值类型的只读引用传递给方法,而不是先复制值然后将其传递。

通过绕过此副本,in应该会更快,但是在我的测试中,它似乎一点都没有更快。

我正在使用BenchMarkDotNet来对我的代码进行基准测试。代码如下:

public struct Input
{
    public decimal Number1 { get; set; }
    public decimal Number2 { get; set; }
}

public class InBenchmarking
{
    const int loops = 50000000;
    Input inputInstance;

    public InBenchmarking()
    {
        inputInstance = new Input
        {
        };
    }

    [Benchmark]
    public decimal DoSomethingRefLoop()
    {
        decimal result = 0M;
        for (int i = 0; i < loops; i++)
        {
            result = DoSomethingRef(ref inputInstance);
        }
        return result;
    }

    [Benchmark]
    public decimal DoSomethingInLoop()
    {
        decimal result = 0M;
        for (int i = 0; i < loops; i++)
        {
            result = DoSomethingIn(inputInstance);
        }
        return result;
    }


    [Benchmark(Baseline = true)]
    public decimal DoSomethingLoop()
    {
        decimal result = 0M;
        for (int i = 0; i < loops; i++)
        {
            result = DoSomething(inputInstance);
        }
        return result;
    }

    public decimal DoSomething(Input input)
    {
        return input.Number1;
    }

    public decimal DoSomethingIn(in Input input)
    {
        return input.Number1;
    }

    public decimal DoSomethingRef(ref Input input)
    {
        return input.Number1;
    }
}

如您所见,我包含一个循环以使用“ref”关键字,该关键字也通过引用传递,但不是只读的。这似乎确实更快。

该测试的结果是:
             Method |     Mean |     Error |    StdDev | Scaled | ScaledSD |
------------------- |---------:|----------:|----------:|-------:|---------:|
 DoSomethingRefLoop | 20.15 ms | 0.3967 ms | 0.6058 ms |   0.41 |     0.03 |
  DoSomethingInLoop | 48.88 ms | 0.9756 ms | 2.5529 ms |   0.98 |     0.08 |
    DoSomethingLoop | 49.84 ms | 1.0872 ms | 3.1367 ms |   1.00 |     0.00 |

因此,使用“in”似乎根本不快。我觉得有些事情可能以我无法预料的方式进行了优化,这就是性能差异的原因。我尝试将结构的大小最多增加16个十进制字段,但同样,in和按值之间没有区别。

如何构造基准测试,以真正看到in,ref和按值传递之间的区别?

最佳答案

问题是您使用的是非readonly结构,因此编译器正在DoSomethingIn方法中创建输入参数的防御性副本。

发生这种情况是因为您使用的是Number1属性的getter方法,并且编译器不确定结构状态是否会因此而改变(并且由于参数作为只读引用传递,因此无效) 。

如果您像这样编辑结构:

public readonly struct Input
{
    public decimal Number1 { get; }
    public decimal Number2 { get; }
}

并再次运行基准测试,使用in方法将获得与使用ref方法相同的性能,就像您最初的假设一样。

注意:不是readonly struct修饰符,您也可以通过直接暴露字段来解决此问题,如下所示:
public struct Input
{
    public decimal Number1;
    public decimal Number2;
}

重点是,如here所述:



编辑#2:以进一步阐明为什么需要readonly struct修饰符(同样,inref readonly相同),这是文档中的另一段:

关于C#7.2关键字效果,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/48142900/

10-11 22:29
查看更多