C#7.2关键字性能

Min*_*ata 8 c# performance benchmarking

我试图测试添加到C#中的"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;
    }
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,我包含一个循环来使用"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 |
Run Code Online (Sandbox Code Playgroud)

所以使用"in"似乎并不快.我觉得有可能以某种我没预料到的方式对某些东西进行优化,并且这可以解释性能差异.我已经尝试将结构的大小增加到16个十进制字段,但同样,它在in和by值之间没有区别.

如何构建我的基准测试以真正看到in,ref和pass by value之间的区别?

Ser*_*694 7

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

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

如果您像这样编辑结构:

public readonly struct Input
{
    public decimal Number1 { get; }
    public decimal Number2 { get; }
}
Run Code Online (Sandbox Code Playgroud)

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

注:readonly struct修饰不是强制性的,你可以通过暴露领域直接为好,这样解决这个问题:

public struct Input
{
    public decimal Number1;
    public decimal Number2;
}
Run Code Online (Sandbox Code Playgroud)

问题的关键是,如前所述这里,认为:

编译器无法知道任何成员方法是否修改了结构的状态.为确保不修改对象,编译器会创建一个副本并使用该副本调用成员引用.任何修改都是针对该防御性副本.

编辑#2:进一步澄清为什么readonly struct需要修饰符(再次,in是相同的ref readonly),这是文档中的另一段:

[...]其他时候,您可能想要创建一个不可变的结构.然后你总是可以通过readonly参考.这种做法会删除在访问用作in参数的结构的方法时发生的防御性副本.