C# 7.2 在关键字性能方面
C# 7.2 In Keyword Performance
我正在尝试测试添加到 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、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 所述,即:
The compiler cannot know if any member method modifies the state of the struct. To ensure that the object is not modified, the compiler creates a copy and calls member references using that copy. Any modifications are to that defensive copy.
编辑 #2: 以进一步阐明为什么需要 readonly struct
修饰符(同样,in
与 ref readonly
), 这是文档中的另一段:
[...] Other times, you may want to create an immutable struct. Then you can always pass by readonly reference. That practice removes the defensive copies that take place when you access methods of a struct used as an in parameter.
我正在尝试测试添加到 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、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 所述,即:
The compiler cannot know if any member method modifies the state of the struct. To ensure that the object is not modified, the compiler creates a copy and calls member references using that copy. Any modifications are to that defensive copy.
编辑 #2: 以进一步阐明为什么需要 readonly struct
修饰符(同样,in
与 ref readonly
), 这是文档中的另一段:
[...] Other times, you may want to create an immutable struct. Then you can always pass by readonly reference. That practice removes the defensive copies that take place when you access methods of a struct used as an in parameter.