C#7.2引入了reference semantics with value-types,除此之外,微软还开发了类似Span<T>
and ReadOnlySpan<T>
的类型,可以提高需要在连续内存区域上执行操作的应用程序的性能。
根据文档,可能提高性能的一种方法是通过向这些类型的参数添加in
修饰符来引用不可变结构:
void PerformOperation(in SomeReadOnlyStruct value)
{
}
我想知道的是我是否应该使用像ReadOnlySpan<T>
这样的类型来做这件事。我是否应该声明接受只读范围的方法:
void PerformOperation<T>(in ReadOnlySpan<T> value)
{
}
或者只是喜欢:
void PerformOperation<T>(ReadOnlySpan<T> value)
{
}
前者是否会提供后者的任何性能优势?我找不到任何方向明确建议的文档,但我确实找到了this example,他们演示了一个接受ReadOnlySpan
并且没有使用{{}的方法1}}修饰符。
答案 0 :(得分:5)
这里的关键因素是尺寸; Span<T>
/ ReadOnlySpan<T>
故意非常小,因此跨度和参考到跨度之间的差异很小。这里in
的一个关键用法是更大的只读结构,以避免重要的堆栈复制;请注意,有一个权衡:in
实际上是ref
,所以你要为所有访问添加一个额外的间接层,除非JIT看到你在做什么并且工作了一些伏都教。当然:如果类型不将自己声明为readonly
,那么在调用之前会自动添加堆栈副本以保留语义。
答案 1 :(得分:2)
我设置了以下基准测试类:
public class SpanBenchmarks
{
private const int Iterations = 100_000;
private byte[] _data;
private LargeStruct _control;
[GlobalSetup]
public void GlobalSetup()
{
_data = new byte[1000];
new Random().NextBytes(_data);
_control = new LargeStruct(_data[0], _data[1], _data[2], _data[3], _data[4], _data[5]);
}
[Benchmark]
public void PassSpanByValue()
{
for (int i = 0; i < Iterations; i++) AcceptSpanByValue(_data);
}
[Benchmark]
public void PassSpanByRef()
{
for (int i = 0; i < Iterations; i++) AcceptSpanByRef(_data);
}
[Benchmark]
public void PassLargeStructByValue()
{
for (int i = 0; i < Iterations; i++) AcceptLargeStructByValue(_control);
}
[Benchmark]
public void PassLargeStructByRef()
{
for (int i = 0; i < Iterations; i++) AcceptLargeStructByRef(_control);
}
private int AcceptSpanByValue(ReadOnlySpan<byte> span) => span.Length;
private int AcceptSpanByRef(in ReadOnlySpan<byte> span) => span.Length;
private decimal AcceptLargeStructByValue(LargeStruct largeStruct) => largeStruct.A;
private decimal AcceptLargeStructByRef(in LargeStruct largeStruct) => largeStruct.A;
private readonly struct LargeStruct
{
public LargeStruct(decimal a, decimal b, decimal c, decimal d, decimal e, decimal f)
{
A = a;
B = b;
C = c;
D = d;
E = e;
F = f;
}
public decimal A { get; }
public decimal B { get; }
public decimal C { get; }
public decimal D { get; }
public decimal E { get; }
public decimal F { get; }
}
}
我用这个重复了三次相同的基准测试工作,每次都得到类似的结果:
BenchmarkDotNet=v0.10.13, OS=Windows 10 Redstone 3 [1709, Fall Creators Update] (10.0.16299.248)
Intel Core i7-4790 CPU 3.60GHz (Haswell), 1 CPU, 8 logical cores and 4 physical cores
Frequency=3507500 Hz, Resolution=285.1033 ns, Timer=TSC
.NET Core SDK=2.1.300-preview2-008354
[Host] : .NET Core 2.0.6 (CoreCLR 4.6.26212.01, CoreFX 4.6.26212.01), 64bit RyuJIT
DefaultJob : .NET Core 2.0.6 (CoreCLR 4.6.26212.01, CoreFX 4.6.26212.01), 64bit RyuJIT
Method | Mean | Error | StdDev |
----------------------- |----------:|----------:|----------:|
PassSpanByValue | 641.71 us | 0.1758 us | 0.1644 us |
PassSpanByRef | 642.62 us | 0.1524 us | 0.1190 us |
PassLargeStructByValue | 390.78 us | 0.2633 us | 0.2463 us |
PassLargeStructByRef | 35.33 us | 0.3446 us | 0.3055 us |
使用大型结构作为控件,我确认通过引用而不是值传递它们时会有显着的性能优势。但是,通过引用或值传递Span<T>
之间没有显着的性能差异。