《ASP.NET Core 高性能系列》Span和Memory

1、Span<T>概述

  原文:Provides a type- and memory-safe representation of a contiguous region of arbitrary memory.api

中文的翻译不许确,这里给出比较厚道的翻译:提供类型T安全、连续的内存区域的表达方式.数组

   
(图1:Span<T>定义,不是全图)

 

   这里出现高阶语法 readonly ref struct,下面是msdn给的语言规范(或者其核心意义),估计你们会看晕, 安全

     Span<T> 而且不能跨 await 和 yield 边界使用。 此外,对两个方法的调用(Equals(Object) 和 GetHashCode)将引起一个 NotSupportedException。由于锁定在堆栈上,因此也不要试图让其成为作为静态成员。数据结构

 

我先给出最简单的解释:ide

  Span<T>是微软为了给.NET提供了一个高效的内存操纵元素,而定义的一个数据结构,为了高效的初衷,将Span<T>自身锁定在堆栈上(内存连续,且处理高效)性能

注意:是Span<T>自身!!!Span<T> 实例一般用于保存数组或某个数组的一部分的元素。spa

 2、Span<T>可用来作哪些事

  2.1 不得不提的 Slice.net

  切片这种东西,在GO,Rust中太寻常了(PS:固然对于C++的表示不屑),对于C#而言,这是一个性能提高不可或缺的概念,pwa

Span基本上就是这个概念的翻版.因此其中有诸多方法就是切片.翻译

  

 

 

 

 

 可见微软为Span提供了诸多相似于原来的String中的不少方法,具体查阅地址: Span的扩展方法

 

 2.1 切片是其本质,是对原有对象的投影(或部分投影)

  以前咱们要实现高效的操做,如字符串类的操做,数组类的操做,

  这里应该尤为注意,不见得你使用Span就高效了,明白它的设计初衷:Slice! 特别是会不断产生新的碎片和构造新对象的场景.(因而可知对于String的操做产生了诸多

新碎片这样的场景是尤为好用的)

  咱们看看以下的场景,你们以为哪一个效率会更高

            int[] array = new int[10000];
            Span<int> arraySpan = array;

            Stopwatch stopwatch = new Stopwatch();
            stopwatch.Start();
            for (int ctr = 0; ctr < arraySpan.Length; ctr++)
                arraySpan[ctr] = arraySpan[ctr] * arraySpan[ctr]/3;
            stopwatch.Stop();
            Console.WriteLine(stopwatch.Elapsed);

            array = new int[10000];
            stopwatch.Reset();
            stopwatch.Start();
            for (int ctr = 0; ctr < array.Length; ctr++)
                array[ctr] = array[ctr] * array[ctr]/3;
            Console.WriteLine(stopwatch.Elapsed);        

  结果按照咱们的原则你就知道,不用Span效果会更好,下图是realse模式下发布的,总体上可知:不用Span会更快,因此切片是它的本质!你们看看GO的切片就知道了.

  

 

 3.1 Span<T>能够不只投影常见对象还能够是从Marshal , stackalloc分配的而来的

 

var native = Marshal.AllocHGlobal(100);
Span<byte> nativeSpan;
unsafe
{
    nativeSpan = new Span<byte>(native.ToPointer(), 100);
}
byte data = 0;
for (int ctr = 0; ctr < nativeSpan.Length; ctr++)
    nativeSpan[ctr] = data++;

int nativeSum = 0;
foreach (var value in nativeSpan)
    nativeSum += value;

Console.WriteLine($"The sum is {nativeSum}");
Marshal.FreeHGlobal(native);

 

byte data = 0;
Span<byte> stackSpan = stackalloc byte[100];
for (int ctr = 0; ctr < stackSpan.Length; ctr++)
    stackSpan[ctr] = data++;

int stackSum = 0;
foreach (var value in stackSpan)
    stackSum += value;

Console.WriteLine($"The sum is {stackSum}");

  

3、Memory<T>概述

  和Span<T>相似,它一样表示连续内存区域。区别是没有Span<T>堆栈上的限制,没有 readonly ref struct 这样的申明了.

这意味着 Memory<T> 能够放置在托管堆上,而 Span<T> 不能。 所以,Memory<T> 结构与 Span<T> 实例没有相同的限制。

具体而言:它可用做类中的字段。它可跨 await 和 yield 边界使用。除了 Memory<T>以外,还可使用 System.ReadOnlyMemory<T> 来表示不可变或只读内存。

 

 

  这里有园友从C++源码的角度进行分析,这里提取下面两段,供你们参阅(连接地址),

Span 与 Memory 的区别:

  1.Memory<T> 保存 原有的对象地址、子内容的开始地址 与 子内容的长度,大体状况下图:

  

 

  如上文所说,Span被微软锁定在堆栈上,

  2.Span 保存子内容的开始地址与长度,不保存原始对象的地址,大体以下图:

  

 

 

若是这就是真实状况,可见Span脱离不了堆栈的环境的,否则计算不了真实的切片地址的.

 

3、使用上的预测和建议

  1.相似于string这样的操做会Span大有用处(由于会产生不少新的中间数据产生开销)

  2.不管span仍是memory设计初衷就是Slice,使用场景是那些会不断产生新的开销、新的对象的场景.

  3.其实全部的性能提高根本让CPU运行的指令更少了,减小了没必要要的开销

相关文章
相关标签/搜索