【5min+】閃電光速拳? .NetCore 中的Span

系列介紹

簡介

【五分鐘的DotNet】是一個利用您的碎片化時間來學習和豐富.net知識的博文系列。它所包含了.net體系中可能會涉及到的方方面面,比如C#的小細節,AspnetCore,微服務中的.net知識等等。
5min+不是超過5分鐘的意思,"+"是知識的增加。so,它是讓您花費5分鐘以下的時間來提升您的知識儲備量。

正文

在dotnet core2.x之後,引入了一個叫做Span<T>的類型。如果您的項目已經升級到了新版的dotnet core 以及使用C# 7+。您會發現我們曾經使用的許許多多類型都增加了一個擴展方法“AsSpan()”。在Vs中小手一點就會出現:


var s = ("xxx").AsSpan();
var s1 = new byte[10].AsSpan();
//.......more

那麼這個傢伙到底是個什麼東西?怎麼用呢?

先來扒一扒它的內部方法:

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();

    //.....
}

這裏只展示它部分的方法,但是關鍵的一點我們可以看到:它是一個結構性(struct 關鍵字)。

而且!!而且!!! 你沒看錯,它還加了一個ref關鍵字。

所以按照我們在上一篇文章中介紹過的 .net中的棧和堆,我們猜想這種結構類型的數據應該是存放在內存棧中,具有很快的訪問速度。而且它擁有了ref關鍵字,證明它具有ref結構體的特點:

  • 不能對 ref struct 裝箱
  • ref struct 類型不能實現接口
  • 不能將 ref struct 聲明爲類或常規結構的字段成員
  • 不能聲明異步方法中屬於 ref struct 類型的本地變量
  • 無法在迭代器中聲明 ref struct 本地變量
  • 無法捕獲 Lambda 表達式或本地函數中的 ref struct 變量

而且根據它公開的這些方法,我們會發現它有點類似我們常用的幾個基礎類型:string 、 byte[] ……。

所以直覺告訴我們,它應該是一個拿來存放數據的類型。

so,來看看MSDN - Magazine中它的解釋:

System.Span<T> 是在 .NET 中發揮關鍵作用的新值類型。使用它,可以表示任意內存的相鄰區域,無論相應內存是與託管對象相關聯,還是通過互操作由本機代碼提供,亦或是位於堆棧上。除了具有上述用途外,它仍能確保安全訪問和高性能特性,就像數組一樣。

果不其然,和我們猜想的一樣。那麼它出現的意義是什麼呢? 性能!!!!

而且是超級快的性能。大家都知道以往如果我們想提高數據間的操作效率(比如數據偏移、裁剪等),就只能使用指針來操作內存中的數據。這樣雖然一波操作猛如虎,但是寫起來費勁不說,我們還得將傳統的C#代碼設置爲不安全代碼,除了添加unsafe關鍵字之外還需要打開項目中執行不安全代碼的選項。

所以,有沒有辦法既不操作指針而又有高性能呢? 好吧,Span大爺來了。

Span在C# 7.x中被引入,所以它的年齡還算比較小,也是因爲這些原因。以往的項目可能沒有辦法使用它。

它到底有多快

大家一般都是想直接看東西,所以我寫了一份對比的代碼。功能很簡單,都是截取字符串中的一部分代碼,並且進行多次的循環操作。

執行結果我都驚呆了:

x

是的,您沒有看錯。差距不是一般的大。

其實剛開始我以爲Span並沒有什麼作用,因爲我將數據源(圖中的compareStr)僅僅設置爲了幾個單詞。然後對他們進行了1億的循環操作,但是最後的結果只有很小的差距,不到百分之30。

後來我想了一下,應該讓數據更貼近現實,於是就將一張圖片轉換爲base64然後作爲數據源。結果驚呆了,差了接近百倍。而且隨着循環次數和對數據源的操作次數的增多,Span和傳統字符串之間的性能差距更大。

傳說中的閃電光速拳到底有多快呢

x

它爲什麼這麼快

它與傳統的string操作比起來爲什麼會具有這麼快的速度呢? 按照我們之前的一些猜想和msdn所給出的一點信息,我們可以得到以下的結論:

  • 它分配堆棧上而不是在託管堆。
  • 它所創建的數據是內存連續的,因此具有更快的遍歷速度。

這些特點和string等原有類型比起來就非常的具有優勢了:原來對string操作涉及到大量的字符串分配和內存複製。所以當操作的數據量小的時候還好,但是隨着操作次數和處理數據量的增加之後,這是非常消耗性能的。

x

Span會給我們帶來什麼

那麼,既然它擁有如此高的性能,那麼我們該在什麼地方使用它呢?

這很簡單,如果您以前有對大量字符串進行截取或者處理的地方,一般都可以替換爲Span。(爲什麼是一般呢😏)

除了string可以轉換爲span之外,其它的byte[],char[]等等都可以轉換爲span進行操作。所以這是非常值得高興的一件事情,它會爲我們數據處理帶來顯著的性能提升。比如字節流緩衝,視頻流的處理,數據的加密解密等等操作都可以使用Span來完成了。

so,在現在的.NETCore runtime中,您會發現大量的類中都開始使用了Span。

x

而且,Span爲我們實現了ExplicitImplicit,所以我們可以直接將支持的數組類型賦值給Span: (如果您不瞭解這兩個關鍵字:戳這兒)

var arr = new byte[10];
Span<byte> bytes = arr; // 直接將byte[]賦值給Span

心動了嗎?瞭解以下Span,並且嘗試着使用它吧。

但是,請注意!! Span也是具有缺點的:因爲只能存放在內存棧中,所以它不具有線程安全,它無法跨異步操作。還有它ref結構的原因,無法裝箱拆箱等。

那麼如果我們需要跨線程共享數據,又想擁有高性能怎麼辦呢? 別急,下一期咱們再來談。😜

最後,小聲說一句:創作不易,點個推薦吧😇

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章