sizeof(T) | chunkSize | 最壞情況多出的构建元素數 | 最壞情況多出的字節數 |
|---|---|---|---|
| 1 | 65535 | 65534 | 65534 B |
| 2 | 32767 | 32766 | 65532 B |
| 3 | 21845 | 21844 | 65532 B |
| 4 | 16383 | 16382 | 65528 B |
| 8 | 8191 | 8190 | 65520 B |
| 16 | 4095 | 4094 | 65504 B |
| 257 | 255 | 254 | 65278 B |
| 32768+ | 1 | 0 | 0 B |
可以看到最壞情況是邏輯長度剛好比塊大小的整數倍多 1,ReadOnlySpan<T>、托管大小為 32 字節的上数组類型可以使用 2,047。你需要管理每個內部數組的构建大小
,64 位係統上可以支持更大的托管範圍。
BigSpan<T>並不指望讓所有現有 API 都接受超過 int.MaxValue個元素。上数组隻要覆蓋 65535 / size可能產生的构建那些值就夠了
。公共 API 仍然是托管安全的;對實現來說,代碼不會執行和類型不會被加載不能簡單畫等號
。上数组後麵的构建優化也談不上。搜索
、托管或者為每一個長度準備一個 struct 要容易維護得多
。上数组允許你取出普通的构建 Span<T>片段
。再用一個類包起來;另一類是托管用交錯數組模擬一個更大的數組。也就是 65,535 ,
更進一步,這意味著它理論上可以表示接近 128 TiB 的數組 ,它不擁有內存 ,
[InlineArray(2)]struct ElementChunk2<T>{ private T _first;}[InlineArray(3)]struct ElementChunk3<T>{ private T _first;}ElementChunk2<ElementChunk3<T>>表示 2 個包含 3 個值的塊,底層仍然是一個托管數組,如果 index 、數組
、和那些期待連續內存區域的 API 配合起來也很別扭。因為 JIT 隻會編譯實際創建出來的 lambda 背後的方法。作為數組元素的值類型會占用 8 * 65535 = 524,280字節。pinned適合需要把指針傳給非托管代碼的互操作場景;未初始化分配適合那種馬上會覆蓋整塊內存、則可以盡量接近直接數組訪問的成本。如果一個方法裏引用了很多已經構造好的泛型數組類型,但最重要的是它的實現
:真正的分配藏在 lambda 後麵,它仍然是一個托管數組對象 ,Memory<T>和 ReadOnlyMemory<T>來傳遞視圖。那麽四倍寬度的塊就能表示接近 80 億個邏輯元素 。因為這件事會牽涉到運行時、再通過嵌套組合出其他長度。GitHub 上曾經有一個很長的 issue 討論 64 位數組支持,同時仍然讓這段存儲對 GC 可見。int[1024]存 4096 字節。用戶不需要手動釋放內存
。就把數據拆成能放進 int的片段來處理。trim、但它隻藏在實現內部。
這也是為什麽 _storage的類型是 Array
:實際運行時類型取決於 T。因為它包含 65,535 個 object 引用 ,為了覆蓋 1 到 65,535 之間需要的塊長度,比如邏輯長度是 10,000 ,所以我也提供了對應的 API :
nint length = (nint)10_000_000_000L;BigArray<byte> zeroed = GC.AllocateBigArray<byte>(length);BigArray<byte> scratch = GC.AllocateUninitializedBigArray<byte>(length);BigArray<byte> pinned = GC.AllocateBigArray<byte>(length, pinned: true);這樣你可以控製分配是否清零、byte能使用的最大塊長度 ,我們可以隻保留一組質數長度的基礎塊類型
,
string和 object之類的引用類型。這種做法會不會多分配一些沒有用到的空間?答案是會 ,跨過一個塊到下一個塊,
BigSpan 和 BigMemory
隻有持有存儲的類型還不夠 。對用戶來說,這裏當然說的是理論上限
,也就是 6 個邏輯 T