中山网站建设收费标准,做淘宝需要知道什么网站,增城网站建设服务,做网站收入来源表Span这个东西出来很久了#xff0c;居然因为5.0又火起来了。特别感谢RC兄弟提出这个话题。相关知识在大多数情况下#xff0c;C#开发时#xff0c;我们只使用托管内存。而实际上#xff0c;C#为我们提供了三种类型的内存#xff1a;堆栈内存 - 最快速的内存#xff0c;能… Span这个东西出来很久了居然因为5.0又火起来了。特别感谢RC兄弟提出这个话题。 相关知识在大多数情况下C#开发时我们只使用托管内存。而实际上C#为我们提供了三种类型的内存堆栈内存 - 最快速的内存能够做到极快的分配和释放。堆栈内存使用时需要用stackalloc进行分配。堆栈的一个特点是空间非常小通常小于1 MB适合CPU缓存。试图分配更多堆栈会报出StackOverflowException错误并终止进程另一个特点是生命周期非常短 - 方法结束时堆栈会与方法的内存一起释放。stackalloc通常用于必须不分配任何托管内存的短操作。一个例子是在corefx中记录快速记录ETW事件要求尽可能快并且需要很少的内存。非托管内存 - 通过Marshal.AllocHGlobal或xMarshal.AllocCoTaskMem方法分配在非托管堆上的内存。这个内存对GC不可见并且必须通过Marshal.FreeHGlobal或Marshal.FreeCoTaskMem的显式调用来释放。使用非托管内存最主要的目的是不给GC增加额外的压力所以最经常的使用方式是在分配大量没有指针的值类型时使用。在Kestrel的代码中很多地方用到了非托管内存。托管内存 - 大多数代码中最常用的内存需要用new操作符来分配。之所以称为托管managed因为它是被GC垃圾管理器管理的由GC决定何时释放内存而不需要开发人员考虑。GC又将托管对象根据大小85000字节分为大对象和小对象。两个对象的分配方式、速度和位置都有不同小对象相对快点大对象相对慢点。另外两种对象的GC回收成本也不一样。问题的产生问个问题写了这么多年的C#我们有用过指针吗有没有想过为什么我们用个例子来回答这个问题一个字符串正常它是一个托管对象。如果我们想解析整个字符串我们会这么写int Parse(string managedMemory);
那么如果我们想只解析一部分字符串该怎么写int Parse(string managedMemory, int startIndex, int length);
现在我们转到非托管内存上unsafe int Parse(char* pointerToUnmanagedMemory, int length);
unsafe int Parse(char* pointerToUnmanagedMemory, int startIndex, int length);
再延伸一下我们写几个用于复制内存的功能void CopyT(T[] source, T[] destination);
void CopyT(T[] source, int sourceStartIndex, T[] destination, int destinationStartIndex, int elementsCount);
unsafe void CopyT(void* source, void* destination, int elementsCount);
unsafe void CopyT(void* source, int sourceStartIndex, void* destination, int destinationStartIndex, int elementsCount);
unsafe void CopyT(void* source, int sourceLength, T[] destination);
unsafe void CopyT(void* source, int sourceStartIndex, T[] destination, int destinationStartIndex, int elementsCount);
是不是很复杂而且看上去并不安全所以问题并不在于我们能不能用而在于这种支持会让代码变得复杂而且并不安全 - 直到Span出现。Span在定义中Span就是一个简单的值类型。它真正的价值在于允许我们与任何类型的连续内存一起工作。这些所谓的连续内存包括非托管内存缓冲区数组和子串字符串和子字符串在使用中Span确保了内存和数据安全而且几乎没有开销。使用Span要使用Span需要设置开发语言为C# 7.2以上并引用System.Memory到项目。PropertyGroupLangVersion7.2/LangVersion
/PropertyGroup
使用低版本编译器会报错Error CS8107 Feature ref structs is not available in C# 7.0. Please use language version 7.2 or greater.。 Span使用时最简单的可以把它想象成一个数组它会做所有的指针运算同时内部又可以指向任何类型的内存。例如我们可以为非托管内存创建SpanSpanbyte stackMemory stackalloc byte[256];IntPtr unmanagedHandle Marshal.AllocHGlobal(256);
Spanbyte unmanaged new Spanbyte(unmanagedHandle.ToPointer(), 256);
Marshal.FreeHGlobal(unmanagedHandle);
从T[]到Span的隐式转换char[] array new char[] { i, m, p, l, i, c, i, t };
Spanchar fromArray array;此外还有ReadOnlySpan可以用来处理字符串或其他不可变类型ReadOnlySpanchar fromString Hello world.AsSpan();Span创建完成后就跟普通的数组一样有一个Length属性和一个允许读写的index因此使用时就和一般的数组一样使用就好。看看Span常用的一些定义、属性和方法Span(T[] array);
Span(T[] array, int startIndex);
Span(T[] array, int startIndex, int length);
unsafe Span(void* memory, int length);int Length { get; }
ref T this[int index] { get; set; }SpanT Slice(int start);
SpanT Slice(int start, int length);void Clear();
void Fill(T value);void CopyTo(SpanT destination);
bool TryCopyTo(SpanT destination);我们用Span来实现一下文章开头的复制内存的功能int Parse(ReadOnlySpanchar anyMemory);
int CopyT(ReadOnlySpanT source, SpanT destination);
看看是不是非常简单而且使用Span时运行性能极佳。关于Span的性能网上有很多评测关注的兄弟可以自己去看。Span的限制Span支持所有类型的内存所以它也会有相当严格的限制。在上面的例子中使用的是堆栈内存。所有指向堆栈的指针都不能存储在托管堆上。因为方法结束时堆栈会被释放指针会变成无效值如果再使用就是内存溢出。因此Span实例也不能驻留在托管堆上而只能驻留在堆栈上。这又引出一些限制。Span不能是非堆栈类型的字段如果在类中设置Span字段它将被存储在堆中。这是不允许的class Impossible
{Spanbyte field;
}
不过从C# 7.2开始在其他仅限堆栈的类型中有Span字段是可以的ref struct TwoSpansT
{public SpanT first;public SpanT second;
}
Span不能有接口实现接口实现意味着数据会被装箱。而装箱意味着存储在堆中。同时为了防止装箱Span必须不实现任何现有的接口例如最容易想到的IEnumerable。也许某一天C#会允许定义由结构体实现的结口Span不能是异步方法的参数异步在C#里绝对是个好东西。不过对于Span是另一件事。异步方法会创建一个AsyncMethodBuilder构建器构建器会创建一个异步状态机。异步状态机会将方法的参数放到堆上。所以Span不能用作异步方法的参数。Span不能是泛型的代入参数看下面的代码Spanbyte Allocate() new Spanbyte(new byte[256]);void CallAndPrintT(FuncT valueProvider)
{object value valueProvider.Invoke();Console.WriteLine(value.ToString());
}void Demo()
{FuncSpanbyte spanProvider Allocate;CallAndPrintSpanbyte(spanProvider);
}
同样也是装箱的原因。 上面是Span的内容。下面简单说一下另一个经常跟Span一起提的内容MemoryMemoryMemory是一个新的数据类型它只能指向托管内存所以不具有仅限堆栈的限制。Memory可以从托管数组、字符串或IOwnedMemory中创建传递给异步方法或存储在类的字段中。当需要Span时就调用它的Span属性。它会根据需要创建Span。然后在当前范围内使用它。看一下Memory的主要定义、属性和方法public readonly struct MemoryT
{private readonly object _object;private readonly int _index;private readonly int _length;public SpanT Span { get; }public MemoryT Slice(int start)public MemoryT Slice(int start, int length)public MemoryHandle Pin()
}
使用也很简单byte[] buffer ArrayPoolbyte.Shared.Rent(16000 * 8);while ((bytesRead await fileStream.ReadAsync(buffer, 0, buffer.Length)) 0)
{ParseBlock(new ReadOnlyMemorybyte(buffer, start: 0, length: bytesRead));
}void ParseBlock(ReadOnlyMemorybyte memory)
{ReadOnlySpanbyte slice memory.Span;
}
总结Span存在很长时间了只是5.0做了一些优化。用好了对代码是很好的补充和优化用不好就会有给自己刨很多个坑。所以耗子尾汁。喜欢就来个三连让更多人因你而受益