ReadOnlySpan <t>参数应该使用“in”修饰符吗?

时间:2018-03-15 11:01:12

标签: c# performance c#-7.2

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}}修饰符。

2 个答案:

答案 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>之间没有显着的性能差异。