Dotnet中Span, Memory和ReadOnlySequence之淺見

過年啦,寫個短點的。同時,提前給大家拜個年。

總有小夥伴們跑過來討論關於Span和Memory的使用,眼瞅是最近關於Span的文章有點多,看飛了。

今天寫這個,就是往回拉一拉。

寫之前,先聲明一下。這些內容是我自己使用的一些經驗,並不代表這些類的全部內容就是這些,只是說,我是這麼用的,而且用得很好。

1. Span

Span在我的概念中,就是一個快速的同步訪問器。

就這麼簡單。

Span很快。在我前邊關於Span的文章中分析過,可以移步【傳送門】去看。而且,它與foreach一起使用也很快,主要是因爲Span的GetEnumerator使用了引用返回。

你看,Span本身就被設計成了一個非常快的東西。

同時,Span是同步的。也就是說,它沒有提供任何異步的方法和屬性。

說到爲什麼Span是同步的,這倒是一個問題。我們需要從根上來找找。Span背後的連續內存塊,主要來自於以下幾個方面:

  • 數組的切片
  • Memory<T>
  • 非託管指針void*
  • stackalloc

其中,第一個是堆上分配的數組的一部分。第二個是基於連續內存的。第三個非託管void*本身就是同步的。

第四個單獨說一下。stackalloc提供的是在線程的堆棧上分配內存。如果Span可以使用異步,會導致一個線程可以訪問另一個線程的堆棧。顯然這是不安全和不合理的。所以,保持Span同步是必須的。

所以,Span就是一個性能非常好的,針對連續內存的同步訪問器。

2. Memory

Memory,就是一個實際的內存塊。

與Span不同,Memory可以在異步流中使用,同時,它還提供了獲取同步訪問器的方法Memory<T>.Span()

Memory可以有多種來源,例如:

  • 數組切片
  • MemoryMarshal的各種Create方法,例如MemoryMarshal.CreateFromPinnedArray()這樣的。

第一個是最基本的用法,從數組T[]中取一個切片成爲Memory。

第二個方法會複雜一些,用了一個特殊的方法來創建Memory。像上邊的例子,CreateFromPinnedArray用了一個已經固定的數組。在Dotnet中,可以通過固定一個對象,來禁止GC移動對象。這在將Memory傳遞給非託管對象時非常有用。

總之,Memory就是一個實際的內存塊。這個內存塊可以被用到任何地方,並可以使用它的同步訪問器Span進行訪問。

3. ReadOnlyX

印象中有三種:ReadOnlySpan、ReadOnlyMemory、ReadOnlySequence。

沒什麼特別的,就是ReadOnly,只讀啦。

前兩個,ReadOnlySpan、ReadOnlyMemory,就是Span和Memory對應的只讀對象。

4. ReadOnlySequence

ReadOnlySequence也不算複雜,就是一個ReadOnlyMemory元素的序列。

基於操作系統的內存管理,有時候Memory不是連續的,可能會分片段,所以就需要有個結構來表示一個Memory鏈/Memory列表類似的序列。這是ReadOnlySequence的由來,而它本身也是一個ReadOnlyMemory的列表。

同時,它也提供了一些屬性來優化序列中包含一個元素的情況:

  • IsSingleSegment,用來快速檢查是否只包含一個內存項
  • FirstSpan,該速訪問ReadOnlySpan訪問器的第一個內存項

因此雖然被定義爲序列,但處理單個元素,例如單個Span或Memory也容易很多。

這就是今天的全部內容了。

有沒有跟你用的不一樣?

文章最後,再次祝大家牛年大吉,萬事勝意~

微信公衆號:老王Plus

掃描二維碼,關注個人公衆號,可以第一時間得到最新的個人文章和內容推送

本文版權歸作者所有,轉載請保留此聲明和原文鏈接

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