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之间的区别?
问题是您使用的是非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参数的结构的方法时发生的防御性副本.