C Span 原始碼解讀和應用實踐

2021-10-10 12:46:16 字數 4032 閱讀 6175

public sealed class string

public sealed class stringbuilder : iserializable

}return this;}}

public readonly struct int32

}

怎麼樣,這些通用 & 基礎的類都在大力對接span / readonlyspan,更別說複雜型別了,其地位不言自明哈,接下來我們就從 span 本身的機制聊起。

靈活運用 span 解決工作中的實際問題我相信大家應該沒什麼毛病了,有了這個基礎再從 span 的原始碼 和 使用者態 和大家一起深度剖析,從原始碼開始吧。

public readonly ref struct span
上面**的ref struct可以看出,這個 span 是只可以分配在棧上的值型別,然後就是裡面的 _pointer 和 _length 兩個例項字段,不知道看完這兩個字段腦子裡是不是有一幅圖,大概是這樣的。

可以清晰的看出,span 就是用來對映一段可以連續訪問的記憶體位址,空間大小由 length 控制,開始位置由 _pointer 指定,是不是像極了指標???,是的,語言團隊要保證你的程式高效能,還得照護你的人身安全,出了各種手段,真是煞費苦心! ???

雖然圖已經畫了,但還是有很多朋友希望眼見為實,必須實操演練,嘿嘿,無懼任何挑戰,那我先把上面的圖化成**:

static void main(string args)

;var span = new span(nums);

console.readline();

}

接下來我用 windbg 把執行緒棧中的 span 也找出來。

0:000> !clrstack -l

os thread id: 0x181c (0)

child sp ip call site

locals:

0x000000963277e618 = 0x000001e956b8ab10

0x000000963277e608 = 0x000001e956b8ab20

從最後一行**可以看出:span 的棧位址是 0x000000963277e608,棧內容是:0x000001e956b8ab20,按照圖的理論: 0x000001e956b8ab20 應該是 nums 陣列元素 1 的記憶體位址,可以用 dp 驗證一下。

0:000> dp 0x000001e956b8ab20

000001e9`56b8ab20 00000002`00000001 00000004`00000003

000001e9`56b8ab30 00000006`00000005 00000000`00000000

000001e9`56b8ab40 00007ffc`3e6c4388 00000000`00000000

從上面三行記憶體位址來看,陣列的:1,2,3,4,5,6依次排列,有些朋友可能有點小疑問,為啥 nums 的記憶體位址不是指向陣列元素 1 的呢? 那我來普及一下吧,先用 dp 喚出陣列的記憶體位址。

0:000> dp 0x000001e956b8ab10

000001e9`56b8ab10 00007ffc`3e69f090 00000000`00000006

000001e9`56b8ab20 00000002`00000001 00000004`00000003

000001e9`56b8ab30 00000006`00000005 00000000`00000000

可以看出,第一排為:00007ffc3e69f090 0000000000000006, 前面的 8 byte 表示 陣列 的 方法表位址,後面的 8byte 表示 6 ,也就是說陣列有 6個元素,不信的話我截一張圖:

span 是由 _pointer + length 組成的,剛才的 _pointer 也給大家演示了,那 length 的值在**呢? 因為 span 是 struct,所以需要用 dp 把剛才的執行緒棧最小的棧位址打出來就可以了。

到這裡,我覺得我講的已經夠清楚了,如果還有點懵的話可以仔細想一想哈。

span的應用場景真的是太多了,不可能在這篇一一枚舉,這裡我就舉兩個例子吧,讓大家能夠感受到 span 的強大即可。

案例:如何高效的計算出使用者輸入的值10+20?

1) 傳統 substring 做法

傳統的做法很簡單,擷取唄,**如下:

static void main(string args)

+=");

console.readline();

}

結果是很輕鬆的算出來了,但你仔細想想這裡是不是有點什麼問題,比如說為了從 word 中扣出 num,我用了兩次 substring,就意味著會在 託管堆 上生成兩個 string,如果說我執行 1w 次話,那託管堆上會不會有 2w 個 string 呢? 修改**如下:

for (int i = 0; i < 10000; i++)

然後看一下 託管堆 上 string 的個數

0:000> !dumpheap -type string -stat

statistics:

mt count totalsize class name

00007ffc53a81e18 20167 556538 system.string

託管堆上有 20167 個,挺恐怖的,真的是給 gc 添麻煩哈,這裡還有 167 個是系統自帶的,接下來的問題是有沒有辦法替換 substring 從而不生成臨時string呢?

2) 新式 span 做法

如果看懂了 span 結構圖,你就應該會使用 _pointer + length 將 string 進行切片處理,對不對,**如下:

for (int i = 0; i < 10000; i++)

然後在 託管堆 驗證一下,是不是沒有 臨時 string 了?

0:000> !dumpheap -type string -stat

statistics:

mt count totalsize class name

00007ffc53a51e18 167 36538 system.string

可以看到就只有 167 個系統字串,效能也得到了不小的提公升,???。

平時用 span 的時候,更多的會應用到 array 上面,畢竟 array 在託管堆上是連續記憶體,方便 span 在上面畫乙個可視視窗,其實不僅僅是 array,從 .net5 開始在 list 上畫乙個檢視也是可以的,截圖如下:

因為 list 的 curd 會導致底層的 array 忽長忽短或重新分配,也就無法實現物理上的連續記憶體,所以 span 應用到 list 之後,希望list是不可變的,這也是官方的建議。

總的來說,span 在 .net 底層框架中的地位是越來越顯著了,相信 netcore 追求更高更快的效能上 span 一定大有可為,大家趕緊學起來,???

更多高質量乾貨:參見我的 github: dotnetfly

C Span 原始碼解讀和應用實踐

string 對 span readonlyspan 的支援 public sealed class string stringbuilder 對 span readonlyspan 的支援 public sealed class stringbuilder iserializable return...

openTLD 原始碼解讀

首先是run tld 在其次就是tldexample 最後到了初始化函式tldinit 第乙個比較關鍵的函式 bb scan 將影象網格化,將首先 scale 1.2.10 10 21 個規格 在每個規格上打網格 這個函式有乙個比較重要的方法 ntuples 就是重複 因為網格上的點很多點有相同的x...

thinkphp原始碼解讀

thinkphp原始碼解讀 thinkphp原始碼的根目錄下是 index.php,是系統預設的 主頁,index.php中首先檢測的是 php執行環境,如果php版本小於 5.3.0則退出執行,定義是否為除錯模式,定義應用目錄,引入入口檔案。thinkphp是整個框架的入口檔案,在thinkphp...