過年啦,寫個短點的。同時,提前給大家拜個年。
總有小夥伴們跑過來討論關於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 掃描二維碼,關注個人公衆號,可以第一時間得到最新的個人文章和內容推送 本文版權歸作者所有,轉載請保留此聲明和原文鏈接 |