1. 程式人生 > >【5min+】閃電光速拳? .NetCore 中的Span

【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中被引入,所以它的年齡還算比較小,也是因為這些原因。以往的專案可能沒有辦法使用它。

它到底有多快

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

執行結果我都驚呆了:

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

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

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

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

它為什麼這麼快

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

  • 它分配堆疊上而不是在託管堆。
  • 它所建立的資料是記憶體連續的,因此具有更快的遍歷速度。

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

Span會給我們帶來什麼

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

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