传说中的孪生兄弟? Memory and Span
(给DotNet加星标,提升.Net技能)
转自:句幽 cnblogs.com/uoyo/p/12218998.html
【五分钟的.NET】是一个利用您的碎片化时间来学习和丰富.NET知识的博文系列。
它所包含了.NET体系中可能会涉及到的方方面面,比如C#的小细节,AspnetCore,微服务中的.NET知识等等。
5min+不是超过5分钟的意思,"+"是知识的增加。so,它是让您花费5分钟以下的时间来提升您的知识储备量。
正文
在上一篇文章:《闪电光速拳?.NET Core 中的Span》 中我们提到了在.NET Core 2.x 所新增的一个类型:Span。
它与咱们传统使用的基础类型相比具有超高的性能,原因是它减少了大量的内存分配和数据量复制,并且它所分配的数据内存是连续的。
但是您会发现它无法用在我们项目的某些地方,它独特的 ref结构 使它没有办法跨线程使用、更没有办法使用Lambda表达式。
public readonly ref struct Span<T>
{
public void Clear();
public void CopyTo([NullableAttribute(new[] { 0, 1 })] Span<T> destination);
public void Fill(T value);
public Enumerator GetEnumerator();
public Span<T> Slice(int start, int length);
public T[] ToArray();
public override string ToString();
//.....
}
public readonly struct Memory<T>
{
public static Memory<T> Empty { get; }
public bool IsEmpty { get; }
public int Length { get; }
public Span<T> Span { get; }
public void CopyTo([NullableAttribute(new[] { 0, 1 })] Memory<T> destination);
public MemoryHandle Pin();
public Memory<T> Slice(int start, int length);
public T[] ToArray();
public override string ToString();
}
和我们猜想的一样。它少了ref关键字,内部方法也和Span差不多(同样拥有CopyTo,Slice等),但是还是有一些差异,比如多了Pin方法,Span属性等。
被声明为ref struct的结构,叫做“ByRefLike”。所以在我们在进行反射的时候,我们使用Type会看到有这样一个属性:IsByRefLike。
除了 Memory<T>之外,还可以使用 System.ReadOnlyMemory<T> 来表示不可变或只读内存。
这是MSDN给出来的解释,不是我乱编的哈!(虽然和我们上面猜的一模一样)
接下来,我们来看看他们到底有多像:
好吧,为了做该图我已经使用了美工必杀器 - ps
有没有发现,除了名字之外,好像其它的都一模一样😱。甚至直接连注释都懒得改了。
一样却又不一样
既然作为孪生兄弟,必然有一些共通之处。而Memory作为对Span的增强(应该也算不算增强吧),那么内部的实现可能很多会与Span相似。
是的,查看Memory的源代码您就会发现,它的内部某些方法就是通过Span来实现的:
public readonly struct Memory<T>
{
public void CopyTo(Memory<T> destination) => Span.CopyTo(destination.Span);
public T[] ToArray() => Span.ToArray();
}
有关Memory的源代码,您可以点此查看:the source code of Memory
https://github.com/dotnet/runtime/blob/master/src/libraries/System.Private.CoreLib/src/System/Memory.cs。
如何使用
来吧,修改上面的Span会在Task中报错的例子:
public async Task MemoryCanInLambda(Memory<string> buffer)
{
await Task.Factory.StartNew(() =>
{
buffer.Trim("s");
});
}
此时我们就可以在异步中使用Memory了,采用连续内存+指针级别的操作方案来操作数据内容,岂不爽歪歪?
异步的数据交由Memory,同步的数据交由Span,ForExample:
static async Task<int> ChecksumReadAsync(Memory<byte> buffer, Stream stream)
{
int bytesRead = await stream.ReadAsync(buffer);
return Checksum(buffer.Span.Slice(0, bytesRead));
// Or buffer.Slice(0, bytesRead).Span
}
static int Checksum(Span<byte> buffer) { ... }
正是由于Span和Memory带来的巨大性能优化,所以.NET Core的开发者们做了一件非常疯狂的事:为.NET的库添加了数百个重载方法。比如,您现在可以看到我们经常使用的Int.Parse方法居然支持了Span,它的签名是酱紫:
public static Int32 Parse(ReadOnlySpan<char> s,
NumberStyles style = NumberStyles.Integer,
[NullableAttribute(2)] IFormatProvider? provider = null);
除此之外,还有long,double…………甚至连Guid和DateTime都有这样的重载。
还有其它常用的各种类也开始支持以Span作为参数的重载方法了,比如Random、StringBuilder等。
public StringBuilder Append(ReadOnlySpan<char> value);
.NET Core正在为它的实现和使用做巨大的适配工作,C# 从7.x 开始就不断对异步操作和内存分配进行优化,这或许也为我们未来.NET的发展给了一点点提示。加油,伟大的开发人员们。
看完本文有收获?请转发分享给更多人
关注「DotNet」加星标,提升.Net技能
好文章,我在看❤️