DotNETWeekly-io / DotNetWeekly

DotNet weekly newsletter
MIT License
201 stars 3 forks source link

【文章推荐】跟 Stephen Toub 学习 Span #631

Closed gaufung closed 1 month ago

gaufung commented 1 month ago

https://www.youtube.com/watch?v=5KdICNWOfEQ

gaufung commented 1 month ago

image

Span<T> 是高性能 C# 代码的秘诀之一,.NET 社区大佬 Stephen Toub 深入探究了什么是 Span 并且从头完成一个简易版的实现。

首先 Span<T> 要解决什么问题?假设我们现在有一个方法是这样的,

private int Sum(int[] array)
{
    int sum = 0;
    foreach(var val in array) sum += val;
    return sum;
}

如果 Sum 方法的是求和数组的部分内容,那么方法的签名需要修改成这样

private int Sum(int[] array, int offset, int length)
{
    int sum = 0;
    for(int i = offset; i < length; i++) sum += array[offset+i];
    return sum;
}

这样会带来一个问题,就是这个方法只支持 int[] 数据类型,而 C# 中有很多类型都是表示连续的一段空间,比如 List 。所以 Span 这个类型结构就被提出来了,如果仅仅是表示一段连续空间,Span 并没有什么特殊之处,C/C++ 中的指针,或者 C# 中的 unsafe 代码块也能够完成同样的工作,但是 Span 是内存安全的类型,而且还是一个值类型。

readonly ref struct MySpan<T>
{
    private readonly ref T _reference;
    private readonly int _length;

    public MySpan(T[] array)
    {
        _reference = ref MemoryMarshal.GetArrayDataReference(array);
        _length = array.Length;
    }

    public MySpan(ref T reference)
    {
        _reference = ref reference;
        _length = 1;
    }

    public MySpan(ref T reference, int length)
    {
        _reference = ref reference;
        _length = length;
    }

    public ref T this[int index]
    {
        get
        {
            if (index < 0 || index >= _length)
            {
                throw new IndexOutOfRangeException();
            }

            return ref Unsafe.Add(ref _reference, index);
        }
    }

    public MySpan<T> Slice(int offset)
    {
        if (offset < 0 || offset >= _length)
        {
            throw new IndexOutOfRangeException();
        }

        return new MySpan<T>(ref Unsafe.Add(ref _reference, offset), _length - offset);
    }
}