C# 7.2 在关键字性能方面



我正在尝试测试添加到 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 和 by 值之间没有区别。

如何构建基准测试以真正了解 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;
}

关键是,正如这里所说,:

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

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

[...]其他时候,您可能希望创建一个不可变的结构。然后,您始终可以通过只读引用传递。这种做法会删除在访问用作 in 参数的结构的方法时发生的防御性副本

相关内容

  • 没有找到相关文章

最新更新